1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <openssl/pem.h>
4 
5 #include "fd-util.h"
6 #include "user-record-sign.h"
7 #include "fileio.h"
8 
user_record_signable_json(UserRecord * ur,char ** ret)9 static int user_record_signable_json(UserRecord *ur, char **ret) {
10         _cleanup_(user_record_unrefp) UserRecord *reduced = NULL;
11         _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
12         int r;
13 
14         assert(ur);
15         assert(ret);
16 
17         r = user_record_clone(ur, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_STRIP_SECRET|USER_RECORD_STRIP_BINDING|USER_RECORD_STRIP_STATUS|USER_RECORD_STRIP_SIGNATURE|USER_RECORD_PERMISSIVE, &reduced);
18         if (r < 0)
19                 return r;
20 
21         j = json_variant_ref(reduced->json);
22 
23         r = json_variant_normalize(&j);
24         if (r < 0)
25                 return r;
26 
27         return json_variant_format(j, 0, ret);
28 }
29 
30 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
31 
user_record_sign(UserRecord * ur,EVP_PKEY * private_key,UserRecord ** ret)32 int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret) {
33         _cleanup_(json_variant_unrefp) JsonVariant *encoded = NULL, *v = NULL;
34         _cleanup_(user_record_unrefp) UserRecord *signed_ur = NULL;
35         _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
36         _cleanup_free_ char *text = NULL, *key = NULL;
37         size_t signature_size = 0, key_size = 0;
38         _cleanup_free_ void *signature = NULL;
39         _cleanup_fclose_ FILE *mf = NULL;
40         int r;
41 
42         assert(ur);
43         assert(private_key);
44         assert(ret);
45 
46         r = user_record_signable_json(ur, &text);
47         if (r < 0)
48                 return r;
49 
50         md_ctx = EVP_MD_CTX_new();
51         if (!md_ctx)
52                 return -ENOMEM;
53 
54         if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, private_key) <= 0)
55                 return -EIO;
56 
57         /* Request signature size */
58         if (EVP_DigestSign(md_ctx, NULL, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
59                 return -EIO;
60 
61         signature = malloc(signature_size);
62         if (!signature)
63                 return -ENOMEM;
64 
65         if (EVP_DigestSign(md_ctx, signature, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
66                 return -EIO;
67 
68         mf = open_memstream_unlocked(&key, &key_size);
69         if (!mf)
70                 return -ENOMEM;
71 
72         if (PEM_write_PUBKEY(mf, private_key) <= 0)
73                 return -EIO;
74 
75         r = fflush_and_check(mf);
76         if (r < 0)
77                 return r;
78 
79         r = json_build(&encoded, JSON_BUILD_ARRAY(
80                                        JSON_BUILD_OBJECT(JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(signature, signature_size)),
81                                                          JSON_BUILD_PAIR("key", JSON_BUILD_STRING(key)))));
82         if (r < 0)
83                 return r;
84 
85         v = json_variant_ref(ur->json);
86 
87         r = json_variant_set_field(&v, "signature", encoded);
88         if (r < 0)
89                 return r;
90 
91         if (DEBUG_LOGGING)
92                 json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
93 
94         signed_ur = user_record_new();
95         if (!signed_ur)
96                 return log_oom();
97 
98         r = user_record_load(signed_ur, v, USER_RECORD_LOAD_FULL|USER_RECORD_PERMISSIVE);
99         if (r < 0)
100                 return r;
101 
102         *ret = TAKE_PTR(signed_ur);
103         return 0;
104 }
105 
user_record_verify(UserRecord * ur,EVP_PKEY * public_key)106 int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) {
107         _cleanup_free_ char *text = NULL;
108         unsigned n_good = 0, n_bad = 0;
109         JsonVariant *array, *e;
110         int r;
111 
112         assert(ur);
113         assert(public_key);
114 
115         array = json_variant_by_key(ur->json, "signature");
116         if (!array)
117                 return USER_RECORD_UNSIGNED;
118 
119         if (!json_variant_is_array(array))
120                 return -EINVAL;
121 
122         if (json_variant_elements(array) == 0)
123                 return USER_RECORD_UNSIGNED;
124 
125         r = user_record_signable_json(ur, &text);
126         if (r < 0)
127                 return r;
128 
129         JSON_VARIANT_ARRAY_FOREACH(e, array) {
130                 _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
131                 _cleanup_free_ void *signature = NULL;
132                 size_t signature_size = 0;
133                 JsonVariant *data;
134 
135                 if (!json_variant_is_object(e))
136                         return -EINVAL;
137 
138                 data = json_variant_by_key(e, "data");
139                 if (!data)
140                         return -EINVAL;
141 
142                 r = json_variant_unbase64(data, &signature, &signature_size);
143                 if (r < 0)
144                         return r;
145 
146                 md_ctx = EVP_MD_CTX_new();
147                 if (!md_ctx)
148                         return -ENOMEM;
149 
150                 if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0)
151                         return -EIO;
152 
153                 if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) {
154                         n_bad ++;
155                         continue;
156                 }
157 
158                 n_good ++;
159         }
160 
161         return n_good > 0 ? (n_bad == 0 ? USER_RECORD_SIGNED_EXCLUSIVE : USER_RECORD_SIGNED) :
162                 (n_bad == 0 ? USER_RECORD_UNSIGNED : USER_RECORD_FOREIGN);
163 }
164 
user_record_has_signature(UserRecord * ur)165 int user_record_has_signature(UserRecord *ur) {
166         JsonVariant *array;
167 
168         array = json_variant_by_key(ur->json, "signature");
169         if (!array)
170                 return false;
171 
172         if (!json_variant_is_array(array))
173                 return -EINVAL;
174 
175         return json_variant_elements(array) > 0;
176 }
177