diff options
-rw-r--r-- | .gitignore | 9 | ||||
-rw-r--r-- | LICENSE | 15 | ||||
-rw-r--r-- | Makefile | 70 | ||||
-rw-r--r-- | config.mk | 8 | ||||
-rw-r--r-- | libsecauth.h | 29 | ||||
-rw-r--r-- | libsecauth_client_hash.c | 31 | ||||
-rw-r--r-- | libsecauth_format_spec.c | 29 | ||||
-rw-r--r-- | libsecauth_parse_spec.c | 84 | ||||
-rw-r--r-- | libsecauth_server_hash.c | 55 | ||||
-rw-r--r-- | mk/linux.mk | 4 | ||||
-rw-r--r-- | mk/macos.mk | 4 | ||||
-rw-r--r-- | mk/windows.mk | 4 |
12 files changed, 342 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e41dce2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*\#* +*~ +*.o +*.a +*.lo +*.so +*.su +*.dll +*.dylib @@ -0,0 +1,15 @@ +ISC License + +© 2021 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2ee23b3 --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +.POSIX: + +LIB_MAJOR = 1 +LIB_MINOR = 0 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR) + + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OS = linux +# Linux: linux +# Mac OS: macos +# Windows: windows +include mk/$(OS).mk + + + +OBJ =\ + libsecauth_client_hash.o\ + libsecauth_format_spec.o\ + libsecauth_parse_spec.o\ + libsecauth_server_hash.o + +HDR =\ + libsecauth.h + +LOBJ = $(OBJ:.o=.lo) + + +all: libsecauth.a libsecauth.$(LIBEXT) +$(OBJ): $($@:.o=.c) $(HDR) + +libsecauth.a: $(OBJ) + -rm -f -- $@ + $(AR) rc $@ $(OBJ) + $(AR) -s $@ + +libsecauth.$(LIBEXT): $(LOBJ) + $(CC) $(LIBFLAGS) $(LDFLAGS_METHODS) -o $@ $(LOBJ) $(LDFLAGS) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +.c.lo: + $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +install: libsecauth.a libsecauth.$(LIBEXT) + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + cp -- libsecauth.a "$(DESTDIR)$(PREFIX)/lib/" + cp -- libsecauth.h "$(DESTDIR)$(PREFIX)/include/" + cp -- libsecauth.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libsecauth.$(LIBMINOREXT)" + ln -sf -- libsecauth.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libsecauth.$(LIBMAJOREXT)" + ln -sf -- libsecauth.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libsecauth.$(LIBEXT)" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsecauth.$(LIBMAJOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsecauth.$(LIBMINOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsecauth.$(LIBEXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsecauth.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/libsecauth.h" + +clean: + -rm -rf -- *.o *.a *.so *.lo *.su *.dll *.dylib + +.SUFFIXES: +.SUFFIXES: .c .o .lo + +.PHONY: all install uninstall clean diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..f785a40 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = cc + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 +CFLAGS = -std=c99 -Wall -Os +LDFLAGS = -s diff --git a/libsecauth.h b/libsecauth.h new file mode 100644 index 0000000..67c2430 --- /dev/null +++ b/libsecauth.h @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBSECAUTH_H +#define LIBSECAUTH_H + +#include <stdint.h> +#include <stddef.h> + + +/* $secauth${$<prehash>$}${$<xferhash>$}$<client_rounds>$<server_rounds>${$<posthash>$}$<expected> */ + + +struct libsecauth_spec { + const char *prehash; /* secret if empty, all information is stored at client-side */ + const char *xferhash; + const char *posthash; + const char *expected; + uint32_t client_rounds; + uint32_t server_rounds; +}; + + +int libsecauth_parse_spec(struct libsecauth_spec *spec, char *settings); +size_t libsecauth_format_spec(struct libsecauth_spec *spec, char *buffer, size_t buffer_size); + +char *libsecauth_client_hash(const struct libsecauth_spec *spec, const char *password); +int libsecauth_server_hash(const struct libsecauth_spec *spec, const char *inhash, const char *pepper, char **resultp); + + +#endif diff --git a/libsecauth_client_hash.c b/libsecauth_client_hash.c new file mode 100644 index 0000000..66bc8ac --- /dev/null +++ b/libsecauth_client_hash.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include "libsecauth.h" + +#include <crypt.h> +#include <string.h> + + +char * +libsecauth_client_hash(const struct libsecauth_spec *spec, const char *password) +{ + struct crypt_data hashbuf[2]; + const char *hash = password; + uint32_t rounds; + size_t i; + + memset(hashbuf, 0, sizeof(hashbuf)); + + if (spec->prehash && *spec->prehash) { + hash = crypt_r(password, spec->prehash, &hashbuf[1]); + if (!hash) + return NULL; + } + + for (i = 0, rounds = spec->client_rounds; rounds--; i ^= 1) { + hash = crypt_r(hash, spec->xferhash, &hashbuf[i]); + if (!hash) + return NULL; + } + + return strdup(hash); +} diff --git a/libsecauth_format_spec.c b/libsecauth_format_spec.c new file mode 100644 index 0000000..83a08cf --- /dev/null +++ b/libsecauth_format_spec.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include "libsecauth.h" + +#include <inttypes.h> +#include <stdio.h> +#include <string.h> + + +size_t +libsecauth_format_spec(struct libsecauth_spec *spec, char *buffer, size_t buffer_size) +{ + char client_rounds[sizeof("4294967295")]; + char server_rounds[sizeof("4294967295")]; + int i, expected_with_dollars; + client_rounds[0] = '\0'; + server_rounds[0] = '\0'; + if (spec->client_rounds) + sprintf(client_rounds, "%"PRIu32, spec->client_rounds); + if (spec->server_rounds) + sprintf(server_rounds, "%"PRIu32, spec->server_rounds); + expected_with_dollars = spec->expected && strchr(spec->expected, '$'); + i = snprintf(buffer, buffer_size, "$secauth$%s%s%s$%s%s%s$%s$%s$%s%s%s$%s%s%s", + spec->prehash ? "{$" : "", spec->prehash ? spec->prehash : "", spec->prehash ? "$}" : "", + spec->xferhash ? "{$" : "", spec->xferhash ? spec->xferhash : "", spec->xferhash ? "$}" : "", + client_rounds, server_rounds, + spec->posthash ? "{$" : "", spec->posthash ? spec->posthash : "", spec->posthash ? "$}" : "", + expected_with_dollars ? "{$" : "", spec->expected ? spec->expected : "" , expected_with_dollars ? "$}" : ""); + return i > 0 ? (size_t)i : 0; +} diff --git a/libsecauth_parse_spec.c b/libsecauth_parse_spec.c new file mode 100644 index 0000000..2961268 --- /dev/null +++ b/libsecauth_parse_spec.c @@ -0,0 +1,84 @@ +/* See LICENSE file for copyright and license details. */ +#include "libsecauth.h" + +#include <ctype.h> +#include <errno.h> +#include <string.h> + + +static char * +get_subhash(char *s, char **endp) +{ + size_t depth = 0; + char *ret; + + if (s[0] == '{' && s[1] == '$') { + s = ret = &s[2]; + } else { + ret = s; + } + for (; s[0]; s++) { + if (s[0] == '{' && s[1] == '$' && s[-1] == '$') { + depth += 1; + } else if (s[0] == '}' && s[1] == '$' && s[-1] == '$') { + if (!depth) + return NULL; + if (!--depth) { + *s++ = '\0'; + *s++ = '\0'; + return ret; + } + } else if (s[0] == '$' && !depth) { + *s++ = '\0'; + return ret; + } + } + + return NULL; +} + +static int +strtou32(const char *s, uint32_t *valp) +{ + for (*valp = 0; isdigit(*s); s++) { + if (*valp > (UINT32_MAX - (uint32_t)(*s & 15)) / 10) + return -1; + *valp = *valp * 10 + (uint32_t)(*s & 15); + } + return *s ? -1 : 0; +} + +int +libsecauth_parse_spec(struct libsecauth_spec *spec, char *s) +{ + const char *client_rounds, *server_rounds; + size_t slen = strlen(s); + + memset(spec, 0, sizeof(*spec)); + + if (strncmp(s, "$secauth$", sizeof("$secauth$") - 1)) + goto invalid; + s = &s[sizeof("$secauth$") - 1]; + + spec->prehash = get_subhash(s, &s); + spec->xferhash = get_subhash(s, &s); + client_rounds = get_subhash(s, &s); + server_rounds = get_subhash(s, &s); + spec->posthash = get_subhash(s, &s); + spec->expected = get_subhash(s, &s); + + if (client_rounds && strtou32(client_rounds, &spec->client_rounds)) + goto invalid; + if (server_rounds && strtou32(server_rounds, &spec->server_rounds)) + goto invalid; + + if (*s) + goto invalid; + + return 0; + +invalid: + memset(s, 0, slen); + errno = EINVAL; + return -1; +} diff --git a/libsecauth_server_hash.c b/libsecauth_server_hash.c new file mode 100644 index 0000000..6f0d4f0 --- /dev/null +++ b/libsecauth_server_hash.c @@ -0,0 +1,55 @@ +/* See LICENSE file for copyright and license details. */ +#include "libsecauth.h" + +#include <crypt.h> +#include <stdlib.h> +#include <string.h> + + +int +libsecauth_server_hash(const struct libsecauth_spec *spec, const char *inhash, const char *pepper, char **resultp) +{ + struct crypt_data hashbuf[2]; + const char *hash = inhash, *result; + char *posthash = NULL, *p; + uint32_t rounds; + size_t i = 0; + + *resultp = NULL; + memset(hashbuf, 0, sizeof(hashbuf)); + + for (i = 0, rounds = spec->server_rounds; rounds--; i ^= 1) { + hash = crypt_r(hash, spec->xferhash, &hashbuf[i]); + if (!hash) + return -1; + } + + if (pepper) { + posthash = malloc(strlen(spec->posthash) + strlen(pepper) + 2); + if (!posthash) + return -1; + p = stpcpy(posthash, spec->posthash); + if (*posthash && p[-1] == '$') + p -= 1; + stpcpy(p, pepper); + } + + hash = crypt_r(hash, posthash ? posthash : spec->posthash, &hashbuf[i]); + free(posthash); + if (!hash) + return -1; + + result = strrchr(hash, '$'); + result = result ? &result[1] : hash; + + if (resultp) { + *resultp = strdup(result); + if (!*resultp) + return -1; + } + + if (!spec->expected || !*spec->expected) + return 0; + + return !strcmp(result, spec->expected); +} diff --git a/mk/linux.mk b/mk/linux.mk new file mode 100644 index 0000000..24b174e --- /dev/null +++ b/mk/linux.mk @@ -0,0 +1,4 @@ +LIBEXT = so +LIBFLAGS = -shared -Wl,-soname,libsecauth.$(LIBEXT).$(LIB_MAJOR) +LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR) +LIBMINOREXT = $(LIBEXT).$(LIB_VERSION) diff --git a/mk/macos.mk b/mk/macos.mk new file mode 100644 index 0000000..bd92de6 --- /dev/null +++ b/mk/macos.mk @@ -0,0 +1,4 @@ +LIBEXT = dylib +LIBFLAGS = -dynamiclib +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) diff --git a/mk/windows.mk b/mk/windows.mk new file mode 100644 index 0000000..e9602e1 --- /dev/null +++ b/mk/windows.mk @@ -0,0 +1,4 @@ +LIBEXT = dll +LIBFLAGS = -mdll +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) |