Skip to content

Commit

Permalink
uuidgen: add support for hash-based UUIDs
Browse files Browse the repository at this point in the history
Signed-off-by: Philip Prindeville <[email protected]>
  • Loading branch information
pprindeville authored and karelzak committed Sep 5, 2017
1 parent b443c17 commit c6f1ec6
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 9 deletions.
47 changes: 43 additions & 4 deletions misc-utils/uuidgen.1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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/.
124 changes: 119 additions & 5 deletions misc-utils/uuidgen.c
Original file line number Diff line number Diff line change
Expand Up @@ -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}
};

Expand All @@ -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;
Expand All @@ -69,19 +117,82 @@ 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);
break;
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;
Expand All @@ -91,5 +202,8 @@ main (int argc, char *argv[])

printf("%s\n", str);

if (is_hex)
free(name);

return EXIT_SUCCESS;
}
4 changes: 4 additions & 0 deletions tests/expected/uuid/oids
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
3d813cbb-47fb-32ba-91df-831e1593ac29
5df41881-3aed-3515-88a7-2f4a814cf09e
2ed6657d-e927-568b-95e1-2665a8aea6a2
fcdc2122-78d2-59f7-91ed-041a561ef652
31 changes: 31 additions & 0 deletions tests/ts/uuid/oids
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit c6f1ec6

Please sign in to comment.