Merge pull request #4686 from poettering/machine-id-app-specific

Add new "khash" API and add new sd_id128_get_machine_app_specific() function
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2016-12-08 23:24:28 -05:00 committed by GitHub
commit 9258a1cae3
13 changed files with 538 additions and 21 deletions

1
.gitignore vendored
View File

@ -198,6 +198,7 @@
/test-fs-util
/test-fstab-util
/test-glob-util
/test-hash
/test-hashmap
/test-hexdecoct
/test-hostname

View File

@ -397,6 +397,7 @@ MANPAGES_ALIAS += \
man/sd_id128_from_string.3 \
man/sd_id128_get_boot.3 \
man/sd_id128_get_invocation.3 \
man/sd_id128_get_machine_app_specific.3 \
man/sd_id128_is_null.3 \
man/sd_id128_t.3 \
man/sd_is_mq.3 \
@ -750,6 +751,7 @@ man/sd_id128_equal.3: man/sd-id128.3
man/sd_id128_from_string.3: man/sd_id128_to_string.3
man/sd_id128_get_boot.3: man/sd_id128_get_machine.3
man/sd_id128_get_invocation.3: man/sd_id128_get_machine.3
man/sd_id128_get_machine_app_specific.3: man/sd_id128_get_machine.3
man/sd_id128_is_null.3: man/sd-id128.3
man/sd_id128_t.3: man/sd-id128.3
man/sd_is_mq.3: man/sd_is_fifo.3
@ -1531,6 +1533,9 @@ man/sd_id128_get_boot.html: man/sd_id128_get_machine.html
man/sd_id128_get_invocation.html: man/sd_id128_get_machine.html
$(html-alias)
man/sd_id128_get_machine_app_specific.html: man/sd_id128_get_machine.html
$(html-alias)
man/sd_id128_is_null.html: man/sd-id128.html
$(html-alias)

View File

@ -938,7 +938,9 @@ libbasic_la_SOURCES = \
src/basic/alloc-util.h \
src/basic/alloc-util.c \
src/basic/format-util.h \
src/basic/nss-util.h
src/basic/nss-util.h \
src/basic/khash.h \
src/basic/khash.c
nodist_libbasic_la_SOURCES = \
src/basic/errno-from-name.h \
@ -4045,6 +4047,16 @@ test_id128_LDADD = \
tests += \
test-id128
# ------------------------------------------------------------------------------
test_hash_SOURCES = \
src/test/test-hash.c
test_hash_LDADD = \
libsystemd-shared.la
tests += \
test-hash
# ------------------------------------------------------------------------------
bin_PROGRAMS += \

View File

@ -71,13 +71,14 @@
<para>This machine ID adheres to the same format and logic as the
D-Bus machine ID.</para>
<para>This ID uniquely identifies the host. It should be considered "confidential", and must not
be exposed in untrusted environments, in particular on the network. If a stable unique
identifier that is tied to the machine is needed for some application, the machine ID or any
part of it must not be used directly. Instead the machine ID should be hashed with a
cryptographic, keyed hash function, using a fixed, application-specific key. That way the ID
will be properly unique, and derived in a constant way from the machine ID but there will be no
way to retrieve the original machine ID from the application-specific one.</para>
<para>This ID uniquely identifies the host. It should be considered "confidential", and must not be exposed in
untrusted environments, in particular on the network. If a stable unique identifier that is tied to the machine is
needed for some application, the machine ID or any part of it must not be used directly. Instead the machine ID
should be hashed with a cryptographic, keyed hash function, using a fixed, application-specific key. That way the
ID will be properly unique, and derived in a constant way from the machine ID but there will be no way to retrieve
the original machine ID from the application-specific one. The
<citerefentry><refentrytitle>sd_id128_get_machine_app_specific</refentrytitle><manvolnum>3</manvolnum></citerefentry>
API provides an implementation of such an algorithm.</para>
<para>The
<citerefentry><refentrytitle>systemd-machine-id-setup</refentrytitle><manvolnum>1</manvolnum></citerefentry>

View File

@ -44,6 +44,7 @@
<refnamediv>
<refname>sd_id128_get_machine</refname>
<refname>sd_id128_get_machine_app_specific</refname>
<refname>sd_id128_get_boot</refname>
<refname>sd_id128_get_invocation</refname>
<refpurpose>Retrieve 128-bit IDs</refpurpose>
@ -58,6 +59,12 @@
<paramdef>sd_id128_t *<parameter>ret</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_id128_get_machine_app_specific</function></funcdef>
<paramdef>sd_id128_t <parameter>app_id</parameter></paramdef>
<paramdef>sd_id128_t *<parameter>ret</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_id128_get_boot</function></funcdef>
<paramdef>sd_id128_t *<parameter>ret</parameter></paramdef>
@ -74,11 +81,22 @@
<refsect1>
<title>Description</title>
<para><function>sd_id128_get_machine()</function> returns the
machine ID of the executing host. This reads and parses the
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
file. This function caches the machine ID internally to make
retrieving the machine ID a cheap operation.</para>
<para><function>sd_id128_get_machine()</function> returns the machine ID of the executing host. This reads and
parses the <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
file. This function caches the machine ID internally to make retrieving the machine ID a cheap operation. This ID
may be used wherever a unique identifier for the local system is needed. However, it is recommended to use this ID
as-is only in trusted environments. In untrusted environments it is recommended to derive an application specific
ID from this machine ID, in an irreversable (cryptographically secure) way. To make this easy
<function>sd_id128_get_machine_app_specific()</function> is provided, see below.</para>
<para><function>sd_id128_get_machine_app_specific()</function> is similar to
<function>sd_id128_get_machine()</function>, but retrieves a machine ID that is specific to the application that is
identified by the indicated application ID. It is recommended to use this function instead of
<function>sd_id128_get_machine()</function> when passing an ID to untrusted environments, in order to make sure
that the original machine ID may not be determined externally. The application-specific ID should be generated via
a tool like <command>journalctl --new-id128</command>, and may be compiled into the application. This function will
return the same application-specific ID for each combination of machine ID and application ID. Internally, this
function calculates HMAC-SHA256 of the application ID, keyed by the machine ID.</para>
<para><function>sd_id128_get_boot()</function> returns the boot ID
of the executing kernel. This reads and parses the
@ -95,10 +113,10 @@
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details. The
ID is cached internally. In future a different mechanism to determine the invocation ID may be added.</para>
<para>Note that <function>sd_id128_get_boot()</function> and <function>sd_id128_get_invocation()</function> always
return UUID v4 compatible IDs. <function>sd_id128_get_machine()</function> will also return a UUID v4-compatible
ID on new installations but might not on older. It is possible to convert the machine ID into a UUID v4-compatible
one. For more information, see
<para>Note that <function>sd_id128_get_machine_app_specific()</function>, <function>sd_id128_get_boot()</function>
and <function>sd_id128_get_invocation()</function> always return UUID v4 compatible IDs.
<function>sd_id128_get_machine()</function> will also return a UUID v4-compatible ID on new installations but might
not on older. It is possible to convert the machine ID into a UUID v4-compatible one. For more information, see
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
<para>For more information about the <literal>sd_id128_t</literal>
@ -117,12 +135,35 @@
<refsect1>
<title>Notes</title>
<para>The <function>sd_id128_get_machine()</function>, <function>sd_id128_get_boot()</function> and
<function>sd_id128_get_invocation()</function> interfaces are available as a shared library, which can be compiled
and linked to with the <literal>libsystemd</literal> <citerefentry
<para>The <function>sd_id128_get_machine()</function>, <function>sd_id128_get_machine_app_specific()</function>
<function>sd_id128_get_boot()</function> and <function>sd_id128_get_invocation()</function> interfaces are
available as a shared library, which can be compiled and linked to with the
<literal>libsystemd</literal> <citerefentry
project='die-net'><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry> file.</para>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Application-specific machine ID</title>
<para>Here's a simple example for an application specific machine ID:</para>
<programlisting>#include &lt;systemd/sd-id128.h&gt;
#include &lt;stdio.h&gt;
#define OUR_APPLICATION_ID SD_ID128_MAKE(c2,73,27,73,23,db,45,4e,a6,3b,b9,6e,79,b5,3e,97)
int main(int argc, char *argv[]) {
sd_id128_t id;
sd_id128_get_machine_app_specific(OUR_APPLICATION_ID, &amp;id);
printf("Our application ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
return 0;
}</programlisting>
</example>
</refsect1>
<refsect1>
<title>See Also</title>

275
src/basic/khash.c Normal file
View File

@ -0,0 +1,275 @@
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <linux/if_alg.h>
#include <stdbool.h>
#include <sys/socket.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "khash.h"
#include "macro.h"
#include "missing.h"
#include "string-util.h"
#include "util.h"
/* On current kernels the maximum digest (according to "grep digestsize /proc/crypto | sort -u") is actually 32, but
* let's add some extra room, the few wasted bytes don't really matter... */
#define LONGEST_DIGEST 128
struct khash {
int fd;
char *algorithm;
uint8_t digest[LONGEST_DIGEST+1];
size_t digest_size;
bool digest_valid;
};
int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) {
union {
struct sockaddr sa;
struct sockaddr_alg alg;
} sa = {
.alg.salg_family = AF_ALG,
.alg.salg_type = "hash",
};
_cleanup_(khash_unrefp) khash *h = NULL;
_cleanup_close_ int fd = -1;
ssize_t n;
assert(ret);
assert(key || key_size == 0);
/* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
if (isempty(algorithm))
return -EINVAL;
/* Overly long hash algorithm names we definitely do not support */
if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
return -EOPNOTSUPP;
fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
strcpy((char*) sa.alg.salg_name, algorithm);
if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
if (errno == ENOENT)
return -EOPNOTSUPP;
return -errno;
}
if (key) {
if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
return -errno;
}
h = new0(khash, 1);
if (!h)
return -ENOMEM;
h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
if (h->fd < 0)
return -errno;
h->algorithm = strdup(algorithm);
if (!h->algorithm)
return -ENOMEM;
/* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
(void) send(h->fd, NULL, 0, 0);
/* Figure out the digest size */
n = recv(h->fd, h->digest, sizeof(h->digest), 0);
if (n < 0)
return -errno;
if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
return -EOPNOTSUPP;
h->digest_size = (size_t) n;
h->digest_valid = true;
/* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
(void) send(h->fd, NULL, 0, 0);
*ret = h;
h = NULL;
return 0;
}
int khash_new(khash **ret, const char *algorithm) {
return khash_new_with_key(ret, algorithm, NULL, 0);
}
khash* khash_unref(khash *h) {
if (!h)
return NULL;
safe_close(h->fd);
free(h->algorithm);
free(h);
return NULL;
}
int khash_dup(khash *h, khash **ret) {
_cleanup_(khash_unrefp) khash *copy = NULL;
assert(h);
assert(ret);
copy = newdup(khash, h, 1);
if (!copy)
return -ENOMEM;
copy->fd = -1;
copy->algorithm = strdup(h->algorithm);
if (!copy)
return -ENOMEM;
copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
if (copy->fd < 0)
return -errno;
*ret = copy;
copy = NULL;
return 0;
}
const char *khash_get_algorithm(khash *h) {
assert(h);
return h->algorithm;
}
size_t khash_get_size(khash *h) {
assert(h);
return h->digest_size;
}
int khash_reset(khash *h) {
ssize_t n;
assert(h);
n = send(h->fd, NULL, 0, 0);
if (n < 0)
return -errno;
h->digest_valid = false;
return 0;
}
int khash_put(khash *h, const void *buffer, size_t size) {
ssize_t n;
assert(h);
assert(buffer || size == 0);
if (size <= 0)
return 0;
n = send(h->fd, buffer, size, MSG_MORE);
if (n < 0)
return -errno;
h->digest_valid = false;
return 0;
}
int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
struct msghdr mh = {
mh.msg_iov = (struct iovec*) iovec,
mh.msg_iovlen = n,
};
ssize_t k;
assert(h);
assert(iovec || n == 0);
if (n <= 0)
return 0;
k = sendmsg(h->fd, &mh, MSG_MORE);
if (k < 0)
return -errno;
h->digest_valid = false;
return 0;
}
static int retrieve_digest(khash *h) {
ssize_t n;
assert(h);
if (h->digest_valid)
return 0;
n = recv(h->fd, h->digest, h->digest_size, 0);
if (n < 0)
return n;
if ((size_t) n != h->digest_size) /* digest size changed? */
return -EIO;
h->digest_valid = true;
return 0;
}
int khash_digest_data(khash *h, const void **ret) {
int r;
assert(h);
assert(ret);
r = retrieve_digest(h);
if (r < 0)
return r;
*ret = h->digest;
return 0;
}
int khash_digest_string(khash *h, char **ret) {
int r;
char *p;
assert(h);
assert(ret);
r = retrieve_digest(h);
if (r < 0)
return r;
p = hexmem(h->digest, h->digest_size);
if (!p)
return -ENOMEM;
*ret = p;
return 0;
}

53
src/basic/khash.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include <sys/types.h>
#include <sys/uio.h>
#include "macro.h"
typedef struct khash khash;
/* For plain hash functions. Hash functions commonly supported on today's kernels are: crc32c, crct10dif, crc32,
* sha224, sha256, sha512, sha384, sha1, md5, md4, sha3-224, sha3-256, sha3-384, sha3-512, and more.*/
int khash_new(khash **ret, const char *algorithm);
/* For keyed hash functions. Hash functions commonly supported on today's kernels are: hmac(sha256), cmac(aes),
* cmac(des3_ede), hmac(sha3-512), hmac(sha3-384), hmac(sha3-256), hmac(sha3-224), hmac(rmd160), hmac(rmd128),
* hmac(sha224), hmac(sha512), hmac(sha384), hmac(sha1), hmac(md5), and more. */
int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size);
int khash_dup(khash *h, khash **ret);
khash* khash_unref(khash *h);
const char *khash_get_algorithm(khash *h);
size_t khash_get_size(khash *h);
int khash_reset(khash *h);
int khash_put(khash *h, const void *buffer, size_t size);
int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n);
int khash_digest_data(khash *h, const void **ret);
int khash_digest_string(khash *h, char **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(khash*, khash_unref);

View File

@ -1109,4 +1109,8 @@ struct ethtool_link_settings {
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif
#include "missing_syscall.h"

View File

@ -511,3 +511,8 @@ global:
sd_bus_get_exit_on_disconnect;
sd_id128_get_invocation;
} LIBSYSTEMD_231;
LIBSYSTEMD_233 {
global:
sd_id128_get_machine_app_specific;
} LIBSYSTEMD_232;

View File

@ -27,6 +27,7 @@
#include "hexdecoct.h"
#include "id128-util.h"
#include "io-util.h"
#include "khash.h"
#include "macro.h"
#include "random-util.h"
#include "util.h"
@ -181,3 +182,34 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
*ret = make_v4_uuid(t);
return 0;
}
_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
_cleanup_(khash_unrefp) khash *h = NULL;
sd_id128_t m, result;
const void *p;
int r;
assert_return(ret, -EINVAL);
r = sd_id128_get_machine(&m);
if (r < 0)
return r;
r = khash_new_with_key(&h, "hmac(sha256)", &m, sizeof(m));
if (r < 0)
return r;
r = khash_put(h, &app_id, sizeof(app_id));
if (r < 0)
return r;
r = khash_digest_data(h, &p);
if (r < 0)
return r;
/* We chop off the trailing 16 bytes */
memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
*ret = make_v4_uuid(result);
return 0;
}

View File

@ -39,12 +39,12 @@ union sd_id128 {
#define SD_ID128_STRING_MAX 33
char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]);
int sd_id128_from_string(const char *s, sd_id128_t *ret);
int sd_id128_randomize(sd_id128_t *ret);
int sd_id128_get_machine(sd_id128_t *ret);
int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret);
int sd_id128_get_boot(sd_id128_t *ret);
int sd_id128_get_invocation(sd_id128_t *ret);

82
src/test/test-hash.c Normal file
View File

@ -0,0 +1,82 @@
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "alloc-util.h"
#include "log.h"
#include "string-util.h"
#include "khash.h"
int main(int argc, char *argv[]) {
_cleanup_(khash_unrefp) khash *h = NULL, *copy = NULL;
_cleanup_free_ char *s = NULL;
log_set_max_level(LOG_DEBUG);
assert_se(khash_new(&h, NULL) == -EINVAL);
assert_se(khash_new(&h, "") == -EINVAL);
assert_se(khash_new(&h, "foobar") == -EOPNOTSUPP);
assert_se(khash_new(&h, "sha256") >= 0);
assert_se(khash_get_size(h) == 32);
assert_se(streq(khash_get_algorithm(h), "sha256"));
assert_se(khash_digest_string(h, &s) >= 0);
assert_se(streq(s, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"));
s = mfree(s);
assert_se(khash_put(h, "foobar", 6) >= 0);
assert_se(khash_digest_string(h, &s) >= 0);
assert_se(streq(s, "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"));
s = mfree(s);
assert_se(khash_put(h, "piep", 4) >= 0);
assert_se(khash_digest_string(h, &s) >= 0);
assert_se(streq(s, "f114d872b5ea075d3be9040d0b7a429514b3f9324a8e8e3dc3fb24c34ee56bea"));
s = mfree(s);
assert_se(khash_put(h, "foo", 3) >= 0);
assert_se(khash_dup(h, &copy) >= 0);
assert_se(khash_put(h, "bar", 3) >= 0);
assert_se(khash_put(copy, "bar", 3) >= 0);
assert_se(khash_digest_string(h, &s) >= 0);
assert_se(streq(s, "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"));
s = mfree(s);
assert_se(khash_digest_string(copy, &s) >= 0);
assert_se(streq(s, "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"));
s = mfree(s);
h = khash_unref(h);
assert_se(khash_new_with_key(&h, "hmac(sha256)", "quux", 4) >= 0);
assert_se(khash_get_size(h) == 32);
assert_se(streq(khash_get_algorithm(h), "hmac(sha256)"));
assert_se(khash_digest_string(h, &s) >= 0);
assert_se(streq(s, "abed9f8218ab473f77218a6a7d39abf1d21fa46d0700c4898e330ba88309d5ae"));
s = mfree(s);
assert_se(khash_put(h, "foobar", 6) >= 0);
assert_se(khash_digest_string(h, &s) >= 0);
assert_se(streq(s, "33f6c70a60db66007d5325d5d1dea37c371354e5b83347a59ad339ce9f4ba3dc"));
return 0;
}

View File

@ -153,5 +153,11 @@ int main(int argc, char *argv[]) {
assert_se(id128_read_fd(fd, ID128_UUID, &id2) >= 0);
assert_se(sd_id128_equal(id, id2));
assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(f0,3d,aa,eb,1c,33,4b,43,a7,32,17,29,44,bf,77,2e), &id) >= 0);
assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(f0,3d,aa,eb,1c,33,4b,43,a7,32,17,29,44,bf,77,2e), &id2) >= 0);
assert_se(sd_id128_equal(id, id2));
assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(51,df,0b,4b,c3,b0,4c,97,80,e2,99,b9,8c,a3,73,b8), &id2) >= 0);
assert_se(!sd_id128_equal(id, id2));
return 0;
}