1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <libcryptsetup.h>
4 
5 #include "cryptsetup-token-util.h"
6 #include "hexdecoct.h"
7 #include "json.h"
8 #include "luks2-fido2.h"
9 #include "memory-util.h"
10 #include "strv.h"
11 
acquire_luks2_key(struct crypt_device * cd,const char * json,const char * device,const char * pin,char ** ret_keyslot_passphrase,size_t * ret_keyslot_passphrase_size)12 int acquire_luks2_key(
13                 struct crypt_device *cd,
14                 const char *json,
15                 const char *device,
16                 const char *pin,
17                 char **ret_keyslot_passphrase,
18                 size_t *ret_keyslot_passphrase_size) {
19 
20         int r;
21         Fido2EnrollFlags required;
22         size_t cid_size, salt_size, decrypted_key_size;
23         _cleanup_free_ void *cid = NULL, *salt = NULL;
24         _cleanup_free_ char *rp_id = NULL;
25         _cleanup_(erase_and_freep) void *decrypted_key = NULL;
26         _cleanup_(erase_and_freep) char *base64_encoded = NULL;
27         _cleanup_strv_free_erase_ char **pins = NULL;
28 
29         assert(ret_keyslot_passphrase);
30         assert(ret_keyslot_passphrase_size);
31 
32         r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required);
33         if (r < 0)
34                 return r;
35 
36         if (pin) {
37                 pins = strv_new(pin);
38                 if (!pins)
39                         return crypt_log_oom(cd);
40         }
41 
42         /* configured to use pin but none was provided */
43         if ((required & FIDO2ENROLL_PIN) && strv_isempty(pins))
44                 return -ENOANO;
45 
46         r = fido2_use_hmac_hash(
47                         device,
48                         rp_id ?: "io.systemd.cryptsetup",
49                         salt, salt_size,
50                         cid, cid_size,
51                         pins,
52                         required,
53                         &decrypted_key,
54                         &decrypted_key_size);
55         if (r == -ENOLCK) /* libcryptsetup returns -ENOANO also on wrong PIN */
56                 r = -ENOANO;
57         if (r < 0)
58                 return r;
59 
60         /* Before using this key as passphrase we base64 encode it, for compat with homed */
61         r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
62         if (r < 0)
63                 return crypt_log_error_errno(cd, r, "Failed to base64 encode key: %m");
64 
65         *ret_keyslot_passphrase = TAKE_PTR(base64_encoded);
66         *ret_keyslot_passphrase_size = strlen(*ret_keyslot_passphrase);
67 
68         return 0;
69 }
70 
71 /* this function expects valid "systemd-fido2" in json */
parse_luks2_fido2_data(struct crypt_device * cd,const char * json,char ** ret_rp_id,void ** ret_salt,size_t * ret_salt_size,void ** ret_cid,size_t * ret_cid_size,Fido2EnrollFlags * ret_required)72 int parse_luks2_fido2_data(
73                 struct crypt_device *cd,
74                 const char *json,
75                 char **ret_rp_id,
76                 void **ret_salt,
77                 size_t *ret_salt_size,
78                 void **ret_cid,
79                 size_t *ret_cid_size,
80                 Fido2EnrollFlags *ret_required) {
81 
82         _cleanup_free_ void *cid = NULL, *salt = NULL;
83         size_t cid_size = 0, salt_size = 0;
84         _cleanup_free_ char *rp = NULL;
85         int r;
86         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
87         JsonVariant *w;
88         Fido2EnrollFlags required = 0;
89 
90         assert(json);
91         assert(ret_rp_id);
92         assert(ret_salt);
93         assert(ret_salt_size);
94         assert(ret_cid);
95         assert(ret_cid_size);
96         assert(ret_required);
97 
98         r = json_parse(json, 0, &v, NULL, NULL);
99         if (r < 0)
100                 return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m");
101 
102         w = json_variant_by_key(v, "fido2-credential");
103         if (!w)
104                 return -EINVAL;
105 
106         r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
107         if (r < 0)
108                 return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m");
109 
110         w = json_variant_by_key(v, "fido2-salt");
111         if (!w)
112                 return -EINVAL;
113 
114         r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
115         if (r < 0)
116                 return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m");
117 
118         w = json_variant_by_key(v, "fido2-rp");
119         if (w) {
120                 /* The "rp" field is optional. */
121                 rp = strdup(json_variant_string(w));
122                 if (!rp) {
123                         crypt_log_error(cd, "Not enough memory.");
124                         return -ENOMEM;
125                 }
126         }
127 
128         w = json_variant_by_key(v, "fido2-clientPin-required");
129         if (w)
130                 /* The "fido2-clientPin-required" field is optional. */
131                 SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w));
132         else
133                 required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */
134 
135         w = json_variant_by_key(v, "fido2-up-required");
136         if (w)
137                 /* The "fido2-up-required" field is optional. */
138                 SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w));
139         else
140                 required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */
141 
142         w = json_variant_by_key(v, "fido2-uv-required");
143         if (w)
144                 /* The "fido2-uv-required" field is optional. */
145                 SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w));
146         else
147                 required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */
148 
149         *ret_rp_id = TAKE_PTR(rp);
150         *ret_cid = TAKE_PTR(cid);
151         *ret_cid_size = cid_size;
152         *ret_salt = TAKE_PTR(salt);
153         *ret_salt_size = salt_size;
154         *ret_required = required;
155 
156         return 0;
157 }
158