1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2016 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <linux/if_alg.h>
23 #include <sys/socket.h>
25 #include "alloc-util.h"
27 #include "hexdecoct.h"
31 #include "string-util.h"
34 /* On current kernels the maximum digest (according to "grep digestsize /proc/crypto | sort -u") is actually 32, but
35 * let's add some extra room, the few wasted bytes don't really matter... */
36 #define LONGEST_DIGEST 128
41 uint8_t digest[LONGEST_DIGEST+1];
46 int khash_supported(void) {
49 struct sockaddr_alg alg;
51 .alg.salg_family = AF_ALG,
52 .alg.salg_type = "hash",
53 .alg.salg_name = "sha256", /* a very common algorithm */
56 static int cached = -1;
59 _cleanup_close_ int fd1 = -1, fd2 = -1;
60 uint8_t buf[LONGEST_DIGEST+1];
62 fd1 = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
64 /* The kernel returns EAFNOSUPPORT if AF_ALG is not supported at all */
65 if (IN_SET(errno, EAFNOSUPPORT, EOPNOTSUPP))
66 return (cached = false);
71 if (bind(fd1, &sa.sa, sizeof(sa)) < 0) {
72 /* The kernel returns ENOENT if the selected algorithm is not supported at all. We use a check
73 * for SHA256 as a proxy for whether the whole API is supported at all. After all it's one of
74 * the most common hash functions, and if it isn't supported, that's ample indication that
75 * something is really off. */
77 if (IN_SET(errno, ENOENT, EOPNOTSUPP))
78 return (cached = false);
83 fd2 = accept4(fd1, NULL, 0, SOCK_CLOEXEC);
85 if (errno == EOPNOTSUPP)
86 return (cached = false);
91 if (recv(fd2, buf, sizeof(buf), 0) < 0) {
92 /* On some kernels we get ENOKEY for non-keyed hash functions (such as sha256), let's refuse
93 * using the API in those cases, since the kernel is
94 * broken. https://github.com/systemd/systemd/issues/8278 */
96 if (IN_SET(errno, ENOKEY, EOPNOTSUPP))
97 return (cached = false);
106 int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) {
109 struct sockaddr_alg alg;
111 .alg.salg_family = AF_ALG,
112 .alg.salg_type = "hash",
115 _cleanup_(khash_unrefp) khash *h = NULL;
116 _cleanup_close_ int fd = -1;
121 assert(key || key_size == 0);
123 /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
124 if (isempty(algorithm))
127 /* Overly long hash algorithm names we definitely do not support */
128 if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
131 supported = khash_supported();
137 fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
141 strcpy((char*) sa.alg.salg_name, algorithm);
142 if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
149 if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
157 h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
161 h->algorithm = strdup(algorithm);
165 /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
166 (void) send(h->fd, NULL, 0, 0);
168 /* Figure out the digest size */
169 n = recv(h->fd, h->digest, sizeof(h->digest), 0);
172 if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
175 h->digest_size = (size_t) n;
176 h->digest_valid = true;
178 /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
179 (void) send(h->fd, NULL, 0, 0);
187 int khash_new(khash **ret, const char *algorithm) {
188 return khash_new_with_key(ret, algorithm, NULL, 0);
191 khash* khash_unref(khash *h) {
200 int khash_dup(khash *h, khash **ret) {
201 _cleanup_(khash_unrefp) khash *copy = NULL;
206 copy = newdup(khash, h, 1);
211 copy->algorithm = strdup(h->algorithm);
212 if (!copy->algorithm)
215 copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
225 const char *khash_get_algorithm(khash *h) {
231 size_t khash_get_size(khash *h) {
234 return h->digest_size;
237 int khash_reset(khash *h) {
242 n = send(h->fd, NULL, 0, 0);
246 h->digest_valid = false;
251 int khash_put(khash *h, const void *buffer, size_t size) {
255 assert(buffer || size == 0);
260 n = send(h->fd, buffer, size, MSG_MORE);
264 h->digest_valid = false;
269 int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
271 mh.msg_iov = (struct iovec*) iovec,
277 assert(iovec || n == 0);
282 k = sendmsg(h->fd, &mh, MSG_MORE);
286 h->digest_valid = false;
291 static int retrieve_digest(khash *h) {
299 n = recv(h->fd, h->digest, h->digest_size, 0);
302 if ((size_t) n != h->digest_size) /* digest size changed? */
305 h->digest_valid = true;
310 int khash_digest_data(khash *h, const void **ret) {
316 r = retrieve_digest(h);
324 int khash_digest_string(khash *h, char **ret) {
331 r = retrieve_digest(h);
335 p = hexmem(h->digest, h->digest_size);