From c6f1ec68a8b38863efff2a18e30b7272db4fb273 Mon Sep 17 00:00:00 2001 From: Philip Prindeville Date: Mon, 28 Aug 2017 13:36:30 -0600 Subject: [PATCH] uuidgen: add support for hash-based UUIDs Signed-off-by: Philip Prindeville --- misc-utils/uuidgen.1 | 47 +++++++++++++-- misc-utils/uuidgen.c | 124 +++++++++++++++++++++++++++++++++++++-- tests/expected/uuid/oids | 4 ++ tests/ts/uuid/oids | 31 ++++++++++ 4 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 tests/expected/uuid/oids create mode 100755 tests/ts/uuid/oids diff --git a/misc-utils/uuidgen.1 b/misc-utils/uuidgen.1 index 56c2bc7bd27..500e3124c50 100644 --- a/misc-utils/uuidgen.1 +++ b/misc-utils/uuidgen.1 @@ -18,18 +18,41 @@ all UUIDs created on the local system, and among UUIDs created on other systems in the past and in the future. .PP -There are two types of UUIDs which +There are three types of UUIDs which .B uuidgen -can generate: time-based UUIDs and random-based UUIDs. By default +can generate: time-based UUIDs, random-based UUIDs, and hash-based UUIDs. +By default .B uuidgen will generate a random-based UUID if a high-quality random number generator is present. Otherwise, it will choose a time-based UUID. -It is possible to force the generation of one of these two +It is possible to force the generation of one of these first two UUID types by using the .B \-r or .B \-t options. +.PP +The third type of UUID is generated with the +.B \-m +or +.B \-s +options (MD5 or SHA1, respectively), followed by +.BR "\-n " \fInamespace +and +.BR "\-N " \fIname\fR. +The \fInamespace\fR may either be a well-known UUID, or else +an alias to one of the well-known UUIDs defined in RFC 4122, that is +.BR @dns , +.BR @url , +.BR @oid , +or +.BR @x500 . +The \fIname\fR is an arbitrary string value. The generated UUID is the +digest of the concatentation of the namespace UUID and the name value, hashed +with the MD5 or SHA1 algorithms. It is, therefore, a predictable value +which may be useful when UUIDs are being used as handles or nonces for +more complex values or values which shouldn't be disclosed directly. +See the RFC for more information. .SH OPTIONS .TP .BR \-r , " \-\-random" @@ -47,13 +70,29 @@ Display help text and exit. .TP .BR \-V , " \-\-version" Display version information and exit. +.TP +.BR \-m , " \-\-md5" +Use MD5 as the hash algorithm. +.TP +.BR \-s , " \-\-sha1" +Use SHA1 as the hash algorith. +.TP +.BR \-n , " \-\-namespace " \fInamespace\fP +Generate the hash with the \fInamespace\fP prefix. +.TP +.BR \-N , " \-\-name " \fIname\fR +Generate the hash of the \fIname\fR. +.TP +.BR \-x , " \-\-hex" +Interpret name \fIname\fR as a hexidecimal string. .SH "CONFORMING TO" OSF DCE 1.1 .SH AUTHOR .B uuidgen was written by Andreas Dilger for libuuid. .SH SEE ALSO -.BR libuuid (3) +.BR libuuid (3), +.B "RFC 4122" .SH AVAILABILITY The uuidgen command is part of the util-linux package and is available from https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/misc-utils/uuidgen.c b/misc-utils/uuidgen.c index ac950747d7d..c1970695389 100644 --- a/misc-utils/uuidgen.c +++ b/misc-utils/uuidgen.c @@ -29,27 +29,75 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_("Create a new UUID value.\n"), out); fputs(USAGE_OPTIONS, out); - fputs(_(" -r, --random generate random-based uuid\n"), out); - fputs(_(" -t, --time generate time-based uuid\n"), out); + fputs(_(" -r, --random generate random-based uuid\n"), out); + fputs(_(" -t, --time generate time-based uuid\n"), out); + fputs(_(" -n, --namespace ns generate hash-based uuid in this namespace\n"), out); + fputs(_(" -N, --name name generate hash-based uuid from this name\n"), out); + fputs(_(" -m, --md5 generate md5 hash\n"), out); + fputs(_(" -s, --sha1 generate sha1 hash\n"), out); + fputs(_(" -x, --hex interpret name as hex string\n"), out); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(18)); printf(USAGE_MAN_TAIL("uuidgen(1)")); exit(EXIT_SUCCESS); } +static char *unhex(const char *value, size_t *valuelen) +{ + char c, *value2; + unsigned n, x; + + if (*valuelen % 2 != 0) { +badstring: + fprintf(stderr, "%s: not a valid hex string\n", program_invocation_short_name); + errtryhelp(EXIT_FAILURE); + } + + value2 = malloc(*valuelen / 2 + 1); + + for (x = n = 0; n < *valuelen; n++) { + c = value[n]; + if ('0' <= c && c <= '9') + x += c - '0'; + else if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) + x += (c - 'A' + 10) & 0xf; + else + goto badstring; + + if (n % 2 == 0) + x *= 16; + else { + value2[n / 2] = x; + x = 0; + } + } + value2[n / 2] = '\0'; + + *valuelen = (n / 2); + + return value2; +} + int main (int argc, char *argv[]) { int c; - int do_type = 0; + int do_type = 0, is_hex = 0; char str[UUID_STR_LEN]; - uuid_t uu; + char *namespace = NULL, *name = NULL; + size_t namelen = 0; + uuid_t ns, uu; static const struct option longopts[] = { {"random", no_argument, NULL, 'r'}, {"time", no_argument, NULL, 't'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, + {"namespace", required_argument, NULL, 'n'}, + {"name", required_argument, NULL, 'N'}, + {"md5", no_argument, NULL, 'm'}, + {"sha1", no_argument, NULL, 's'}, + {"hex", no_argument, NULL, 'x'}, {NULL, 0, NULL, 0} }; @@ -58,7 +106,7 @@ main (int argc, char *argv[]) textdomain(PACKAGE); atexit(close_stdout); - while ((c = getopt_long(argc, argv, "rtVh", longopts, NULL)) != -1) + while ((c = getopt_long(argc, argv, "rtVhn:N:msx", longopts, NULL)) != -1) switch (c) { case 't': do_type = UUID_TYPE_DCE_TIME; @@ -69,12 +117,53 @@ main (int argc, char *argv[]) case 'V': printf(UTIL_LINUX_VERSION); return EXIT_SUCCESS; + case 'n': + namespace = optarg; + break; + case 'N': + name = optarg; + break; + case 'm': + do_type = UUID_TYPE_DCE_MD5; + break; + case 's': + do_type = UUID_TYPE_DCE_SHA1; + break; + case 'x': + is_hex = 1; + break; case 'h': usage(); default: errtryhelp(EXIT_FAILURE); } + if (namespace) { + if (!name) { + fprintf(stderr, "%s: --namespace requires --name argument\n", program_invocation_short_name); + errtryhelp(EXIT_FAILURE); + } + if (do_type != UUID_TYPE_DCE_MD5 && do_type != UUID_TYPE_DCE_SHA1) { + fprintf(stderr, "%s: --namespace requires --md5 or --sha1\n", program_invocation_short_name); + errtryhelp(EXIT_FAILURE); + } + } else { + if (name) { + fprintf(stderr, "%s: --name requires --namespace argument\n", program_invocation_short_name); + errtryhelp(EXIT_FAILURE); + } + if (do_type == UUID_TYPE_DCE_MD5 || do_type == UUID_TYPE_DCE_SHA1) { + fprintf(stderr, "%s: --md5 or --sha1 require --namespace\n", program_invocation_short_name); + errtryhelp(EXIT_FAILURE); + } + } + + if (name) { + namelen = strlen(name); + if (is_hex) + name = unhex(name, &namelen); + } + switch (do_type) { case UUID_TYPE_DCE_TIME: uuid_generate_time(uu); @@ -82,6 +171,28 @@ main (int argc, char *argv[]) case UUID_TYPE_DCE_RANDOM: uuid_generate_random(uu); break; + case UUID_TYPE_DCE_MD5: + case UUID_TYPE_DCE_SHA1: + if (namespace[0] == '@' && namespace[1] != '\0') { + const uuid_t *uuidptr; + + uuidptr = uuid_get_template(&namespace[1]); + if (uuidptr == NULL) { + fprintf(stderr, "%s: unknown namespace alias '%s'\n", program_invocation_short_name, namespace); + errtryhelp(EXIT_FAILURE); + } + memcpy(ns, *uuidptr, sizeof(ns)); + } else { + if (uuid_parse(namespace, ns) != 0) { + fprintf(stderr, "%s: invalid uuid for namespace '%s'\n", program_invocation_short_name, namespace); + errtryhelp(EXIT_FAILURE); + } + } + if (do_type == UUID_TYPE_DCE_MD5) + uuid_generate_md5(uu, ns, name, namelen); + else + uuid_generate_sha1(uu, ns, name, namelen); + break; default: uuid_generate(uu); break; @@ -91,5 +202,8 @@ main (int argc, char *argv[]) printf("%s\n", str); + if (is_hex) + free(name); + return EXIT_SUCCESS; } diff --git a/tests/expected/uuid/oids b/tests/expected/uuid/oids new file mode 100644 index 00000000000..4644848e842 --- /dev/null +++ b/tests/expected/uuid/oids @@ -0,0 +1,4 @@ +3d813cbb-47fb-32ba-91df-831e1593ac29 +5df41881-3aed-3515-88a7-2f4a814cf09e +2ed6657d-e927-568b-95e1-2665a8aea6a2 +fcdc2122-78d2-59f7-91ed-041a561ef652 diff --git a/tests/ts/uuid/oids b/tests/ts/uuid/oids new file mode 100755 index 00000000000..3b005c5b2aa --- /dev/null +++ b/tests/ts/uuid/oids @@ -0,0 +1,31 @@ +#!/bin/bash + +# This file is part of util-linux. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This file 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 General Public License for more details. + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="oids" + +. $TS_TOPDIR/functions.sh +ts_init "$*" + +: . > $TS_OUTPUT + +uuidgen --md5 --namespace @dns --name "www.widgets.com" >> $TS_OUTPUT + +uuidgen --md5 --namespace @dns --name "www.example.com" >> $TS_OUTPUT + +uuidgen --sha1 --namespace @dns --name "www.example.com" >> $TS_OUTPUT + +uuidgen --sha1 --namespace @oid --hex --name "525400fc0f5e" >> $TS_OUTPUT + +ts_finalize