chiark / gitweb /
tree-wide: remove Lennart's copyright lines
[elogind.git] / src / basic / khash.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <linux/if_alg.h>
4 #include <stdbool.h>
5 #include <sys/socket.h>
6
7 #include "alloc-util.h"
8 #include "fd-util.h"
9 #include "hexdecoct.h"
10 #include "khash.h"
11 #include "macro.h"
12 #include "missing.h"
13 #include "string-util.h"
14 #include "util.h"
15
16 /* On current kernels the maximum digest (according to "grep digestsize /proc/crypto | sort -u") is actually 32, but
17  * let's add some extra room, the few wasted bytes don't really matter... */
18 #define LONGEST_DIGEST 128
19
20 struct khash {
21         int fd;
22         char *algorithm;
23         uint8_t digest[LONGEST_DIGEST+1];
24         size_t digest_size;
25         bool digest_valid;
26 };
27
28 int khash_supported(void) {
29         static const union {
30                 struct sockaddr sa;
31                 struct sockaddr_alg alg;
32         } sa = {
33                 .alg.salg_family = AF_ALG,
34                 .alg.salg_type = "hash",
35                 .alg.salg_name = "sha256", /* a very common algorithm */
36         };
37
38         static int cached = -1;
39
40         if (cached < 0) {
41                 _cleanup_close_ int fd1 = -1, fd2 = -1;
42                 uint8_t buf[LONGEST_DIGEST+1];
43
44                 fd1 = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
45                 if (fd1 < 0) {
46                         /* The kernel returns EAFNOSUPPORT if AF_ALG is not supported at all */
47                         if (IN_SET(errno, EAFNOSUPPORT, EOPNOTSUPP))
48                                 return (cached = false);
49
50                         return -errno;
51                 }
52
53                 if (bind(fd1, &sa.sa, sizeof(sa)) < 0) {
54                         /* The kernel returns ENOENT if the selected algorithm is not supported at all. We use a check
55                          * for SHA256 as a proxy for whether the whole API is supported at all. After all it's one of
56                          * the most common hash functions, and if it isn't supported, that's ample indication that
57                          * something is really off. */
58
59                         if (IN_SET(errno, ENOENT, EOPNOTSUPP))
60                                 return (cached = false);
61
62                         return -errno;
63                 }
64
65                 fd2 = accept4(fd1, NULL, 0, SOCK_CLOEXEC);
66                 if (fd2 < 0) {
67                         if (errno == EOPNOTSUPP)
68                                 return (cached = false);
69
70                         return -errno;
71                 }
72
73                 if (recv(fd2, buf, sizeof(buf), 0) < 0) {
74                         /* On some kernels we get ENOKEY for non-keyed hash functions (such as sha256), let's refuse
75                          * using the API in those cases, since the kernel is
76                          * broken. https://github.com/systemd/systemd/issues/8278 */
77
78                         if (IN_SET(errno, ENOKEY, EOPNOTSUPP))
79                                 return (cached = false);
80                 }
81
82                 cached = true;
83         }
84
85         return cached;
86 }
87
88 int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) {
89         union {
90                 struct sockaddr sa;
91                 struct sockaddr_alg alg;
92         } sa = {
93                 .alg.salg_family = AF_ALG,
94                 .alg.salg_type = "hash",
95         };
96
97         _cleanup_(khash_unrefp) khash *h = NULL;
98         _cleanup_close_ int fd = -1;
99         int supported;
100         ssize_t n;
101
102         assert(ret);
103         assert(key || key_size == 0);
104
105         /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
106         if (isempty(algorithm))
107                 return -EINVAL;
108
109         /* Overly long hash algorithm names we definitely do not support */
110         if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
111                 return -EOPNOTSUPP;
112
113         supported = khash_supported();
114         if (supported < 0)
115                 return supported;
116         if (supported == 0)
117                 return -EOPNOTSUPP;
118
119         fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
120         if (fd < 0)
121                 return -errno;
122
123         strcpy((char*) sa.alg.salg_name, algorithm);
124         if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
125                 if (errno == ENOENT)
126                         return -EOPNOTSUPP;
127                 return -errno;
128         }
129
130         if (key) {
131                 if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
132                         return -errno;
133         }
134
135         h = new0(khash, 1);
136         if (!h)
137                 return -ENOMEM;
138
139         h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
140         if (h->fd < 0)
141                 return -errno;
142
143         h->algorithm = strdup(algorithm);
144         if (!h->algorithm)
145                 return -ENOMEM;
146
147         /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
148         (void) send(h->fd, NULL, 0, 0);
149
150         /* Figure out the digest size */
151         n = recv(h->fd, h->digest, sizeof(h->digest), 0);
152         if (n < 0)
153                 return -errno;
154         if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
155                 return -EOPNOTSUPP;
156
157         h->digest_size = (size_t) n;
158         h->digest_valid = true;
159
160         /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
161         (void) send(h->fd, NULL, 0, 0);
162
163         *ret = h;
164         h = NULL;
165
166         return 0;
167 }
168
169 int khash_new(khash **ret, const char *algorithm) {
170         return khash_new_with_key(ret, algorithm, NULL, 0);
171 }
172
173 khash* khash_unref(khash *h) {
174         if (!h)
175                 return NULL;
176
177         safe_close(h->fd);
178         free(h->algorithm);
179         return mfree(h);
180 }
181
182 int khash_dup(khash *h, khash **ret) {
183         _cleanup_(khash_unrefp) khash *copy = NULL;
184
185         assert(h);
186         assert(ret);
187
188         copy = newdup(khash, h, 1);
189         if (!copy)
190                 return -ENOMEM;
191
192         copy->fd = -1;
193         copy->algorithm = strdup(h->algorithm);
194         if (!copy->algorithm)
195                 return -ENOMEM;
196
197         copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
198         if (copy->fd < 0)
199                 return -errno;
200
201         *ret = TAKE_PTR(copy);
202
203         return 0;
204 }
205
206 const char *khash_get_algorithm(khash *h) {
207         assert(h);
208
209         return h->algorithm;
210 }
211
212 size_t khash_get_size(khash *h) {
213         assert(h);
214
215         return h->digest_size;
216 }
217
218 int khash_reset(khash *h) {
219         ssize_t n;
220
221         assert(h);
222
223         n = send(h->fd, NULL, 0, 0);
224         if (n < 0)
225                 return -errno;
226
227         h->digest_valid = false;
228
229         return 0;
230 }
231
232 int khash_put(khash *h, const void *buffer, size_t size) {
233         ssize_t n;
234
235         assert(h);
236         assert(buffer || size == 0);
237
238         if (size <= 0)
239                 return 0;
240
241         n = send(h->fd, buffer, size, MSG_MORE);
242         if (n < 0)
243                 return -errno;
244
245         h->digest_valid = false;
246
247         return 0;
248 }
249
250 int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
251         struct msghdr mh = {
252                 mh.msg_iov = (struct iovec*) iovec,
253                 mh.msg_iovlen = n,
254         };
255         ssize_t k;
256
257         assert(h);
258         assert(iovec || n == 0);
259
260         if (n <= 0)
261                 return 0;
262
263         k = sendmsg(h->fd, &mh, MSG_MORE);
264         if (k < 0)
265                 return -errno;
266
267         h->digest_valid = false;
268
269         return 0;
270 }
271
272 static int retrieve_digest(khash *h) {
273         ssize_t n;
274
275         assert(h);
276
277         if (h->digest_valid)
278                 return 0;
279
280         n = recv(h->fd, h->digest, h->digest_size, 0);
281         if (n < 0)
282                 return n;
283         if ((size_t) n != h->digest_size) /* digest size changed? */
284                 return -EIO;
285
286         h->digest_valid = true;
287
288         return 0;
289 }
290
291 int khash_digest_data(khash *h, const void **ret) {
292         int r;
293
294         assert(h);
295         assert(ret);
296
297         r = retrieve_digest(h);
298         if (r < 0)
299                 return r;
300
301         *ret = h->digest;
302         return 0;
303 }
304
305 int khash_digest_string(khash *h, char **ret) {
306         int r;
307         char *p;
308
309         assert(h);
310         assert(ret);
311
312         r = retrieve_digest(h);
313         if (r < 0)
314                 return r;
315
316         p = hexmem(h->digest, h->digest_size);
317         if (!p)
318                 return -ENOMEM;
319
320         *ret = p;
321         return 0;
322 }