1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <libcryptsetup.h>
5 #include <string.h>
6 
7 #include "cryptsetup-token.h"
8 #include "cryptsetup-token-util.h"
9 #include "hexdecoct.h"
10 #include "json.h"
11 #include "luks2-fido2.h"
12 #include "memory-util.h"
13 #include "version.h"
14 
15 #define TOKEN_NAME "systemd-fido2"
16 #define TOKEN_VERSION_MAJOR "1"
17 #define TOKEN_VERSION_MINOR "0"
18 
19 /* for libcryptsetup debug purpose */
cryptsetup_token_version(void)20 _public_ const char *cryptsetup_token_version(void) {
21         return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")";
22 }
23 
cryptsetup_token_open_pin(struct crypt_device * cd,int token,const char * pin,size_t pin_size,char ** password,size_t * password_len,void * usrptr)24 _public_ int cryptsetup_token_open_pin(
25                 struct crypt_device *cd, /* is always LUKS2 context */
26                 int token /* is always >= 0 */,
27                 const char *pin,
28                 size_t pin_size,
29                 char **password, /* freed by cryptsetup_token_buffer_free */
30                 size_t *password_len,
31                 void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
32 
33         int r;
34         const char *json;
35         _cleanup_(erase_and_freep) char *pin_string = NULL;
36 
37         assert(!pin || pin_size);
38         assert(token >= 0);
39 
40         /* This must not fail at this moment (internal error) */
41         r = crypt_token_json_get(cd, token, &json);
42         assert(token == r);
43         assert(json);
44 
45         if (pin && memchr(pin, 0, pin_size - 1))
46                 return crypt_log_error_errno(cd, ENOANO, "PIN must be characters string.");
47 
48         /* pin was passed as pin = pin, pin_size = strlen(pin). We need to add terminating
49          * NULL byte to addressable memory*/
50         if (pin && pin[pin_size-1] != '\0') {
51                 pin_string = strndup(pin, pin_size);
52                 if (!pin_string)
53                         return crypt_log_oom(cd);
54         }
55 
56         return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string ?: pin, password, password_len);
57 }
58 
59 /*
60  * This function is called from within following libcryptsetup calls
61  * provided conditions further below are met:
62  *
63  * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-fido2'):
64  *
65  * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device
66  *   (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY
67  *    and token is assigned to at least single keyslot).
68  *
69  * - if plugin defines validate function (see cryptsetup_token_validate below) it must have
70  *   passed the check (aka return 0)
71  */
cryptsetup_token_open(struct crypt_device * cd,int token,char ** password,size_t * password_len,void * usrptr)72 _public_ int cryptsetup_token_open(
73                 struct crypt_device *cd, /* is always LUKS2 context */
74                 int token /* is always >= 0 */,
75                 char **password, /* freed by cryptsetup_token_buffer_free */
76                 size_t *password_len,
77                 void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
78 
79         return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr);
80 }
81 
82 /*
83  * libcryptsetup callback for memory deallocation of 'password' parameter passed in
84  * any crypt_token_open_* plugin function
85  */
cryptsetup_token_buffer_free(void * buffer,size_t buffer_len)86 _public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) {
87         erase_and_free(buffer);
88 }
89 
90 /*
91  * prints systemd-fido2 token content in crypt_dump().
92  * 'type' and 'keyslots' fields are printed by libcryptsetup
93  */
cryptsetup_token_dump(struct crypt_device * cd,const char * json)94 _public_ void cryptsetup_token_dump(
95                 struct crypt_device *cd /* is always LUKS2 context */,
96                 const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
97 
98         int r;
99         Fido2EnrollFlags required;
100         size_t cid_size, salt_size;
101         const char *client_pin_req_str, *up_req_str, *uv_req_str;
102         _cleanup_free_ void *cid = NULL, *salt = NULL;
103         _cleanup_free_ char *rp_id = NULL, *cid_str = NULL, *salt_str = NULL;
104 
105         assert(json);
106 
107         r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required);
108         if (r < 0)
109                 return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
110 
111         r = crypt_dump_buffer_to_hex_string(cid, cid_size, &cid_str);
112         if (r < 0)
113                 return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
114 
115         r = crypt_dump_buffer_to_hex_string(salt, salt_size, &salt_str);
116         if (r < 0)
117                 return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
118 
119         if (required & FIDO2ENROLL_PIN)
120                 client_pin_req_str = "true";
121         else if (required & FIDO2ENROLL_PIN_IF_NEEDED)
122                 client_pin_req_str = NULL;
123         else
124                 client_pin_req_str = "false";
125 
126         if (required & FIDO2ENROLL_UP)
127                 up_req_str = "true";
128         else if (required & FIDO2ENROLL_UP_IF_NEEDED)
129                 up_req_str = NULL;
130         else
131                 up_req_str = "false";
132 
133         if (required & FIDO2ENROLL_UV)
134                 uv_req_str = "true";
135         else if (required & FIDO2ENROLL_UV_OMIT)
136                 uv_req_str = NULL;
137         else
138                 uv_req_str = "false";
139 
140         crypt_log(cd, "\tfido2-credential:" CRYPT_DUMP_LINE_SEP "%s\n", cid_str);
141         crypt_log(cd, "\tfido2-salt: %s\n", salt_str);
142 
143         /* optional fields */
144         if (rp_id)
145                 crypt_log(cd, "\tfido2-rp:   %s\n", rp_id);
146         if (client_pin_req_str)
147                 crypt_log(cd, "\tfido2-clientPin-required:" CRYPT_DUMP_LINE_SEP "%s\n",
148                           client_pin_req_str);
149         if (up_req_str)
150                 crypt_log(cd, "\tfido2-up-required:" CRYPT_DUMP_LINE_SEP "%s\n", up_req_str);
151         if (uv_req_str)
152                 crypt_log(cd, "\tfido2-uv-required:" CRYPT_DUMP_LINE_SEP "%s\n", uv_req_str);
153 }
154 
155 /*
156  * Note:
157  *   If plugin is available in library path, it's called in before following libcryptsetup calls:
158  *
159  *   crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour
160  */
cryptsetup_token_validate(struct crypt_device * cd,const char * json)161 _public_ int cryptsetup_token_validate(
162                 struct crypt_device *cd, /* is always LUKS2 context */
163                 const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) {
164 
165         int r;
166         JsonVariant *w;
167        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
168 
169         assert(json);
170 
171         r = json_parse(json, 0, &v, NULL, NULL);
172         if (r < 0)
173                 return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m.");
174 
175         w = json_variant_by_key(v, "fido2-credential");
176         if (!w || !json_variant_is_string(w)) {
177                 crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-credential' field.");
178                 return 1;
179         }
180 
181         r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
182         if (r < 0)
183                 return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m");
184 
185         w = json_variant_by_key(v, "fido2-salt");
186         if (!w || !json_variant_is_string(w)) {
187                 crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-salt' field.");
188                 return 1;
189         }
190 
191         r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
192         if (r < 0)
193                 return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m.");
194 
195         /* The "rp" field is optional. */
196         w = json_variant_by_key(v, "fido2-rp");
197         if (w && !json_variant_is_string(w)) {
198                 crypt_log_debug(cd, "FIDO2 token data's 'fido2-rp' field is not a string.");
199                 return 1;
200         }
201 
202         /* The "fido2-clientPin-required" field is optional. */
203         w = json_variant_by_key(v, "fido2-clientPin-required");
204         if (w && !json_variant_is_boolean(w)) {
205                 crypt_log_debug(cd, "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean.");
206                 return 1;
207         }
208 
209         /* The "fido2-up-required" field is optional. */
210         w = json_variant_by_key(v, "fido2-up-required");
211         if (w && !json_variant_is_boolean(w)) {
212                 crypt_log_debug(cd, "FIDO2 token data's 'fido2-up-required' field is not a boolean.");
213                 return 1;
214         }
215 
216         /* The "fido2-uv-required" field is optional. */
217         w = json_variant_by_key(v, "fido2-uv-required");
218         if (w && !json_variant_is_boolean(w)) {
219                 crypt_log_debug(cd, "FIDO2 token data's 'fido2-uv-required' field is not a boolean.");
220                 return 1;
221         }
222 
223         return 0;
224 }
225