From adfa8e1265f6155d1a582baa9929af198bb5d4de Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 1 May 2026 17:45:39 +0200 Subject: Misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- DEPENDENCIES | 33 ++++ Makefile | 32 +++- algorithms.h | 45 ++--- argon2/argon2.h | 91 ++++++++++ argon2/hash.c | 36 ++++ argon2/is_algorithm.c | 81 +++++++++ argon2/make_settings.c | 66 +++++++ argon2/prefix.mk | 6 + argon2/suffix.mk | 52 ++++++ argon2/test_supported.c | 59 +++++++ common.h | 114 ++++++++++--- config-coverage-gcc.mk | 11 ++ librecrypt.h | 101 +++++++---- librecrypt_add_algorithm.3 | 23 +++ librecrypt_add_algorithm.c | 101 +++++++++-- librecrypt_algorithms_.c | 67 +++++--- librecrypt_chain_length.c | 3 + librecrypt_check_settings_.c | 255 ++++++++++++++++++++++++++++ librecrypt_common_rfc4848s4_decoding_lut_.c | 58 +++++++ librecrypt_common_rfc4848s4_encoding_lut_.c | 53 ++++++ librecrypt_decode.c | 110 ++++++++++-- librecrypt_decompose_chain.c | 3 + librecrypt_decompose_chain1.c | 5 + librecrypt_encode.c | 22 +++ librecrypt_equal_binary.c | 3 + librecrypt_fill_with_random_.c | 8 +- librecrypt_find_first_algorithm_.c | 12 ++ librecrypt_get_encoding.c | 3 + librecrypt_hash_.c | 177 +++++++++++++++---- librecrypt_make_settings.c | 6 + librecrypt_next_algorithm.c | 6 +- librecrypt_realise_salts.3 | 9 +- librecrypt_realise_salts.c | 154 ++++++++++------- librecrypt_rng_.c | 67 ++++++-- librecrypt_settings_prefix.3 | 52 ++++-- librecrypt_settings_prefix.c | 104 ++++++++++-- librecrypt_test_supported.c | 31 ++-- librecrypt_wipe_str.c | 6 + 38 files changed, 1781 insertions(+), 284 deletions(-) create mode 100644 DEPENDENCIES create mode 100644 argon2/argon2.h create mode 100644 argon2/hash.c create mode 100644 argon2/is_algorithm.c create mode 100644 argon2/make_settings.c create mode 100644 argon2/prefix.mk create mode 100644 argon2/suffix.mk create mode 100644 argon2/test_supported.c create mode 100644 config-coverage-gcc.mk create mode 100644 librecrypt_check_settings_.c create mode 100644 librecrypt_common_rfc4848s4_decoding_lut_.c create mode 100644 librecrypt_common_rfc4848s4_encoding_lut_.c diff --git a/DEPENDENCIES b/DEPENDENCIES new file mode 100644 index 0000000..9b6d40d --- /dev/null +++ b/DEPENDENCIES @@ -0,0 +1,33 @@ +Unconditional dependencies: + libc + +For Argon2 support: + libar2simplified + libar2 + libblake (indirect) + pthread (indirect) + +Build dependencies: + make + sh + echo + true + false + c99 + ar + +Install dependencies: + make + sh + echo + true + false + mkdir + cp + ln + +Notes: + make(1) must support `+=` and `!=` as defined by + The Open Group Base Specifications Issue 8 (the 2024 edition); + strict The Open Group Base Specifications Issue 7 (the 2018 edition) + compliance is not sufficient. diff --git a/Makefile b/Makefile index 549b8fb..08bb33d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ .POSIX: +include argon2/prefix.mk + + CONFIGFILE = config.mk include $(CONFIGFILE) @@ -35,13 +38,20 @@ OBJ_PUBLIC =\ librecrypt_hash.o\ librecrypt_crypt.o\ librecrypt_add_algorithm.o\ - librecrypt_test_supported.o + librecrypt_test_supported.o\ OBJ_PRIVATE =\ + librecrypt_algorithms_.o\ librecrypt_hash_.o\ librecrypt_rng_.o\ librecrypt_fill_with_random_.o\ - librecrypt_find_first_algorithm_.o + librecrypt_find_first_algorithm_.o\ + librecrypt_check_settings_.o\ + $(OBJ_COMMON_RFC4848S4) + +USE_OBJ_COMMON_RFC4848S4 =\ + librecrypt_common_rfc4848s4_encoding_lut_.o\ + librecrypt_common_rfc4848s4_decoding_lut_.o OBJ = $(OBJ_PUBLIC) $(OBJ_PRIVATE) @@ -56,6 +66,14 @@ TEST = $(OBJ:.o=.t) MAN3 = $(OBJ_PUBLIC:.o=.3) MAN7 = librecrypt.7 +all: + +include argon2/suffix.mk + +SRC =\ + $(OBJ:.o=.c)\ + $(HDR) + all: librecrypt.a librecrypt.$(LIBEXT) $(TEST) $(OBJ): $(HDR) @@ -64,19 +82,19 @@ $(TOBJ): $(HDR) $(TEST): librecrypt.a .c.o: - $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + $(CC) -c -o $@ $< $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) .c.lo: - $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) .c.to: - $(CC) -DTEST -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + $(CC) -DTEST -c -o $@ $< $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) .to.t: - $(CC) -o $@ $< librecrypt.a $(LDFLAGS) + $(CC) -o $@ $< librecrypt.a $(LDFLAGS) $(LDFLAGS_MODULES) .c.t: - $(CC) -DTEST -o $@ $< librecrypt.a $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) + $(CC) -DTEST -o $@ $< librecrypt.a $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) $(LDFLAGS) $(LDFLAGS_MODULES) librecrypt.a: $(OBJ) @rm -f -- $@ diff --git a/algorithms.h b/algorithms.h index d38dcf5..c768a4a 100644 --- a/algorithms.h +++ b/algorithms.h @@ -2,24 +2,29 @@ /* included from "common.h" */ +#include "argon2/argon2.h" + + /* ordered by preference */ -#define LIST_ALGORITHMS(X) /* TODO add algorithms */ - - -#define Y(ALGO)\ - HIDDEN size_t librecrypt__##ALGO##__get_prefix(const char *settings, size_t len);\ - HIDDEN unsigned librecrypt__##ALGO##__is_algorithm(const char *settings, size_t len);\ - HIDDEN int librecrypt__##ALGO##__hash(char *restrict out_buffer, size_t size, const char *phrase,\ - size_t len, const char *settings, size_t prefix, void *reserved);\ - HIDDEN int librecrypt__##ALGO##__test_supported(const char *phrase, size_t len, int text,\ - const char *settings, size_t prefix);\ - HIDDEN ssize_t librecrypt__##ALGO##__make_settings(char *out_buffer, size_t size, const char *algorithm,\ - size_t memcost, uintmax_t timecost, int gensalt,\ - ssize_t (*rng)(void *out, size_t n, void *user), void *user);\ - NONSTRING extern const char librecrypt__##ALGO##__encoding_lut[256];\ - extern const unsigned char librecrypt__##ALGO##__decoding_lut[256]; - -#define X(ALGO) IF__##ALGO##__SUPPORTED(Y(ALGO)) -LIST_ALGORITHMS(X) -#undef X -#undef Y +#define LIST_ALGORITHMS(X)\ + X(argon2id)\ + X(argon2d)\ + X(argon2i)\ + X(argon2ds) + + + + +#ifdef REQUIRES_COMMON_RFC4848S4 + +/** + * RFC 4648 §4 implementation of `struct algorithm.encoding_lut_` + */ +NONSTRING extern const char librecrypt_common_rfc4848s4_encoding_lut_[256u]; + +/** + * RFC 4648 §4 implementation of `struct algorithm.decoding_lut_` + */ +extern const unsigned char librecrypt_common_rfc4848s4_decoding_lut_[256u]; + +#endif diff --git a/argon2/argon2.h b/argon2/argon2.h new file mode 100644 index 0000000..cd149da --- /dev/null +++ b/argon2/argon2.h @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ +/* included from "algorithms.h" */ + + +#define IF__argon2i__SUPPORTED(A) +#define IF__argon2d__SUPPORTED(A) +#define IF__argon2id__SUPPORTED(A) +#define IF__argon2ds__SUPPORTED(A) + + +#if defined(SUPPORT_ARGON2I) +# undef IF__argon2i__SUPPORTED +# define IF__argon2i__SUPPORTED(A) A +# define argon2i__HASH_SIZE argon2__HASH_SIZE +# define argon2i__FLEXIBLE_HASH_SIZE argon2__FLEXIBLE_HASH_SIZE +# define argon2i__STRICT_PAD argon2__STRICT_PAD +# define argon2i__PAD argon2__PAD +# define librecrypt__argon2i__hash librecrypt__argon2__hash +# define librecrypt__argon2i__test_supported librecrypt__argon2__test_supported +# define librecrypt__argon2i__encoding_lut librecrypt_common_rfc4848s4_encoding_lut_ +# define librecrypt__argon2i__decoding_lut librecrypt_common_rfc4848s4_decoding_lut_ +HIDDEN PURE unsigned librecrypt__argon2i__is_algorithm(const char *settings, size_t len); +HIDDEN ssize_t librecrypt__argon2i__make_settings(char *out_buffer, size_t size, const char *algorithm, + size_t memcost, uintmax_t timecost, int gensalt, + ssize_t (*rng)(void *out, size_t n, void *user), void *user); +#endif + +#if defined(SUPPORT_ARGON2D) +# undef IF__argon2d__SUPPORTED +# define IF__argon2d__SUPPORTED(A) A +# define argon2d__HASH_SIZE argon2__HASH_SIZE +# define argon2d__FLEXIBLE_HASH_SIZE argon2__FLEXIBLE_HASH_SIZE +# define argon2d__STRICT_PAD argon2__STRICT_PAD +# define argon2d__PAD argon2__PAD +# define librecrypt__argon2d__hash librecrypt__argon2__hash +# define librecrypt__argon2d__test_supported librecrypt__argon2__test_supported +# define librecrypt__argon2d__encoding_lut librecrypt_common_rfc4848s4_encoding_lut_ +# define librecrypt__argon2d__decoding_lut librecrypt_common_rfc4848s4_decoding_lut_ +HIDDEN PURE unsigned librecrypt__argon2d__is_algorithm(const char *settings, size_t len); +HIDDEN ssize_t librecrypt__argon2d__make_settings(char *out_buffer, size_t size, const char *algorithm, + size_t memcost, uintmax_t timecost, int gensalt, + ssize_t (*rng)(void *out, size_t n, void *user), void *user); +#endif + +#if defined(SUPPORT_ARGON2ID) +# undef IF__argon2id__SUPPORTED +# define IF__argon2id__SUPPORTED(A) A +# define argon2id__HASH_SIZE argon2__HASH_SIZE +# define argon2id__FLEXIBLE_HASH_SIZE argon2__FLEXIBLE_HASH_SIZE +# define argon2id__STRICT_PAD argon2__STRICT_PAD +# define argon2id__PAD argon2__PAD +# define librecrypt__argon2id__hash librecrypt__argon2__hash +# define librecrypt__argon2id__test_supported librecrypt__argon2__test_supported +# define librecrypt__argon2id__encoding_lut librecrypt_common_rfc4848s4_encoding_lut_ +# define librecrypt__argon2id__decoding_lut librecrypt_common_rfc4848s4_decoding_lut_ +HIDDEN PURE unsigned librecrypt__argon2id__is_algorithm(const char *settings, size_t len); +HIDDEN ssize_t librecrypt__argon2id__make_settings(char *out_buffer, size_t size, const char *algorithm, + size_t memcost, uintmax_t timecost, int gensalt, + ssize_t (*rng)(void *out, size_t n, void *user), void *user); +#endif + +#if defined(SUPPORT_ARGON2DS) +# undef IF__argon2ds__SUPPORTED +# define IF__argon2ds__SUPPORTED(A) A +# define argon2ds__HASH_SIZE argon2__HASH_SIZE +# define argon2ds__FLEXIBLE_HASH_SIZE argon2__FLEXIBLE_HASH_SIZE +# define argon2ds__STRICT_PAD argon2__STRICT_PAD +# define argon2ds__PAD argon2__PAD +# define librecrypt__argon2ds__hash librecrypt__argon2__hash +# define librecrypt__argon2ds__test_supported librecrypt__argon2__test_supported +# define librecrypt__argon2ds__encoding_lut librecrypt_common_rfc4848s4_encoding_lut_ +# define librecrypt__argon2ds__decoding_lut librecrypt_common_rfc4848s4_decoding_lut_ +HIDDEN PURE unsigned librecrypt__argon2ds__is_algorithm(const char *settings, size_t len); +HIDDEN ssize_t librecrypt__argon2ds__make_settings(char *out_buffer, size_t size, const char *algorithm, + size_t memcost, uintmax_t timecost, int gensalt, + ssize_t (*rng)(void *out, size_t n, void *user), void *user); +#endif + +#if defined(SUPPORT_ARGON2I) || defined(SUPPORT_ARGON2D) || defined(SUPPORT_ARGON2ID) || defined(SUPPORT_ARGON2DS) +# define argon2__HASH_SIZE 32u +# define argon2__FLEXIBLE_HASH_SIZE 1 +# define argon2__STRICT_PAD 0 +# define argon2__PAD '=' +HIDDEN int librecrypt__argon2__hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len, + const char *settings, size_t prefix, void *reserved); +HIDDEN PURE int librecrypt__argon2__test_supported(const char *phrase, size_t len, int text, + const char *settings, size_t prefix, size_t *len_out); +# ifndef REQUIRES_COMMON_RFC4848S4 +# define REQUIRES_COMMON_RFC4848S4 +# endif +#endif diff --git a/argon2/hash.c b/argon2/hash.c new file mode 100644 index 0000000..ff4a4f8 --- /dev/null +++ b/argon2/hash.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include "../common.h" +#ifndef TEST + +#include +#include + + +int +librecrypt__argon2__hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len, + const char *settings, size_t prefix, void *reserved) +{ + /* TODO implement */ + (void) out_buffer; + (void) size; + (void) phrase; + (void) len; + (void) settings; + (void) prefix; + (void) reserved; + return 0; +} + + +#else + + +CONST int +main(void) +{ + return 0; +} + + +#endif +/* TODO test */ diff --git a/argon2/is_algorithm.c b/argon2/is_algorithm.c new file mode 100644 index 0000000..96a889a --- /dev/null +++ b/argon2/is_algorithm.c @@ -0,0 +1,81 @@ +/* See LICENSE file for copyright and license details. */ +#include "../common.h" +#ifndef TEST + + +#define DECLARE_IS_ALGORITHM(ALGO)\ + unsigned\ + librecrypt__##ALGO##__is_algorithm(const char *settings, size_t len)\ + {\ + return len >= sizeof("$"#ALGO"$") - 1u && !strncmp(settings, "$"#ALGO"$", sizeof("$"#ALGO"$") - 1u);\ + } + +IF__argon2i__SUPPORTED(DECLARE_IS_ALGORITHM(argon2i)) +IF__argon2d__SUPPORTED(DECLARE_IS_ALGORITHM(argon2d)) +IF__argon2id__SUPPORTED(DECLARE_IS_ALGORITHM(argon2id)) +IF__argon2ds__SUPPORTED(DECLARE_IS_ALGORITHM(argon2ds)) + + +#else + + +#define CHECK(ALGO, PREFIX, SUFFIX, RET)\ + EXPECT(librecrypt__##ALGO##__is_algorithm(PREFIX SUFFIX, sizeof(PREFIX) - 1u) == (RET)) + + +int +main(void) +{ +#if defined(SUPPORT_ARGON2I) + CHECK(argon2i, "", "", 0u); + CHECK(argon2i, "$argon2$", "", 0u); + CHECK(argon2i, "$argon2i", "$", 0u); + CHECK(argon2i, "$argon2i$", "", 1u); + CHECK(argon2i, "$ARGON2I$", "", 0u); + CHECK(argon2i, "$argon2i$x", "", 1u); + CHECK(argon2i, "$argon2d$", "", 0u); + CHECK(argon2i, "$argon2id$", "", 0u); + CHECK(argon2i, "$argon2ds$", "", 0u); +#endif + +#if defined(SUPPORT_ARGON2D) + CHECK(argon2d, "", "", 0u); + CHECK(argon2d, "$argon2$", "", 0u); + CHECK(argon2d, "$argon2d", "$", 0u); + CHECK(argon2d, "$argon2d$", "", 1u); + CHECK(argon2d, "$ARGON2D$", "", 0u); + CHECK(argon2d, "$argon2d$x", "", 1u); + CHECK(argon2d, "$argon2i$", "", 0u); + CHECK(argon2d, "$argon2id$", "", 0u); + CHECK(argon2d, "$argon2ds$", "", 0u); +#endif + +#if defined(SUPPORT_ARGON2ID) + CHECK(argon2id, "", "", 0u); + CHECK(argon2id, "$argon2$", "", 0u); + CHECK(argon2id, "$argon2id", "$", 0u); + CHECK(argon2id, "$argon2id$", "", 1u); + CHECK(argon2id, "$ARGON2ID$", "", 0u); + CHECK(argon2id, "$argon2id$x", "", 1u); + CHECK(argon2id, "$argon2i$", "", 0u); + CHECK(argon2id, "$argon2d$", "", 0u); + CHECK(argon2id, "$argon2ds$", "", 0u); +#endif + +#if defined(SUPPORT_ARGON2DS) + CHECK(argon2ds, "", "", 0u); + CHECK(argon2ds, "$argon2$", "", 0u); + CHECK(argon2ds, "$argon2ds", "$", 0u); + CHECK(argon2ds, "$argon2ds$", "", 1u); + CHECK(argon2ds, "$ARGON2DS$", "", 0u); + CHECK(argon2ds, "$argon2ds$x", "", 1u); + CHECK(argon2ds, "$argon2i$", "", 0u); + CHECK(argon2ds, "$argon2d$", "", 0u); + CHECK(argon2ds, "$argon2id$", "", 0u); +#endif + + return 0; +} + + +#endif diff --git a/argon2/make_settings.c b/argon2/make_settings.c new file mode 100644 index 0000000..33c13a4 --- /dev/null +++ b/argon2/make_settings.c @@ -0,0 +1,66 @@ +/* See LICENSE file for copyright and license details. */ +#include "../common.h" +#ifndef TEST + + +static ssize_t +make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memcost, uintmax_t timecost, + int gensalt, ssize_t (*rng)(void *out, size_t n, void *user), void *user) +{ + /* Use default RNG if NULL is specified */ + if (!rng) + rng = &librecrypt_rng_; + + /* Adjust `memcost` for algorithm */ + if (!memcost) { + /* Use default memcost if 0 was specified */ + memcost = (size_t)4096u; /* 4 MiB */ + } else { + /* Function takes bytes as memcost, algorithm takes kilobytes */ + int memcost_round_up = memcost % 1024u >= 512u; + memcost >>= 10; + memcost += memcost_round_up ? 1u : 0u; + memcost = memcost ? memcost : 1u; + } + + /* TODO implement */ + (void) out_buffer; + (void) size; + (void) algorithm; + (void) memcost; + (void) timecost; + (void) gensalt; + (void) rng; + (void) user; + return 0; +} + + +#define DECLARE_MAKE_SETTINGS(ALGO)\ + ssize_t\ + librecrypt__##ALGO##__make_settings(char *out_buffer, size_t size, const char *algorithm,\ + size_t memcost, uintmax_t timecost, int gensalt,\ + ssize_t (*rng)(void *out, size_t n, void *user), void *user)\ + {\ + algorithm = algorithm ? algorithm : "$"#ALGO"$";\ + return make_settings(out_buffer, size, algorithm, memcost, timecost, gensalt, rng, user);\ + } + +IF__argon2i__SUPPORTED(DECLARE_MAKE_SETTINGS(argon2i)) +IF__argon2d__SUPPORTED(DECLARE_MAKE_SETTINGS(argon2d)) +IF__argon2id__SUPPORTED(DECLARE_MAKE_SETTINGS(argon2id)) +IF__argon2ds__SUPPORTED(DECLARE_MAKE_SETTINGS(argon2ds)) + + +#else + + +CONST int +main(void) +{ + return 0; +} + + +#endif +/* TODO test */ diff --git a/argon2/prefix.mk b/argon2/prefix.mk new file mode 100644 index 0000000..ff5c01d --- /dev/null +++ b/argon2/prefix.mk @@ -0,0 +1,6 @@ +SUPPORT_ARGON2 = true + +SUPPORT_ARGON2I = $(SUPPORT_ARGON2) +SUPPORT_ARGON2D = $(SUPPORT_ARGON2) +SUPPORT_ARGON2ID = $(SUPPORT_ARGON2) +SUPPORT_ARGON2DS = $(SUPPORT_ARGON2) diff --git a/argon2/suffix.mk b/argon2/suffix.mk new file mode 100644 index 0000000..01be2df --- /dev/null +++ b/argon2/suffix.mk @@ -0,0 +1,52 @@ +SUPPORT_ANY_ARGON2 = ($(SUPPORT_ARGON2I) || $(SUPPORT_ARGON2D) || $(SUPPORT_ARGON2ID) || $(SUPPORT_ARGON2DS)) + +HDR += argon2/argon2.h + +OBJ_ARGON2 !=\ + if $(SUPPORT_ANY_ARGON2); then echo\ + argon2/hash.o\ + argon2/test_supported.o\ + argon2/is_algorithm.o\ + argon2/make_settings.o\ + ;fi + +OBJ_PRIVATE += $(OBJ_ARGON2) + +OBJ_COMMON_RFC4848S4 = $(USE_OBJ_COMMON_RFC4848S4) + +CPPFLAGS_ARGON2 !=\ + if $(SUPPORT_ARGON2I); then echo\ + -DSUPPORT_ARGON2I\ + ;fi;\ + if $(SUPPORT_ARGON2D); then echo\ + -DSUPPORT_ARGON2D\ + ;fi;\ + if $(SUPPORT_ARGON2ID); then echo\ + -DSUPPORT_ARGON2ID\ + ;fi;\ + if $(SUPPORT_ARGON2DS); then echo\ + -DSUPPORT_ARGON2DS\ + ;fi + +CFLAGS_ARGON2 !=\ + if $(SUPPORT_ANY_ARGON2); then echo\ + -pthread\ + ;fi + +LDFLAGS_ARGON2 !=\ + if $(SUPPORT_ANY_ARGON2); then echo\ + -lar2simplified\ + -lar2\ + -lblake\ + -pthread\ + ;fi + +CPPFLAGS_MODULES += $(CPPFLAGS_ARGON2) +CFLAGS_MODULES += $(CFLAGS_ARGON2) +LDFLAGS_MODULES += $(LDFLAGS_ARGON2) + + +clean: clean-argon2 +clean-argon2: + -rm -f -- argon2/*.o argon2/*.lo argon2/*.su argon2/*.to argon2/*.t + -rm -f -- argon2/*.gch argon2/*.gcov argon2/*.gcno argon2/*.gcda diff --git a/argon2/test_supported.c b/argon2/test_supported.c new file mode 100644 index 0000000..1589191 --- /dev/null +++ b/argon2/test_supported.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include "../common.h" +#ifndef TEST + +#include + + +int +librecrypt__argon2__test_supported(const char *phrase, size_t len, int text, const char *settings, size_t prefix, size_t *len_out) +{ + uintmax_t hashsize; + int r; + + /* We don't care about password content, arbitrary binary is supported */ + (void) phrase; + (void) text; + +#define RANGE(MIN, MAX) (uintmax_t)(MIN), (uintmax_t)(MAX) +#define BASE64 librecrypt_common_rfc4848s4_decoding_lut_, argon2__PAD, argon2__STRICT_PAD + + /* Validate string format and parameters */ + r = librecrypt_check_settings_(settings, prefix, + "$%*$%sm=%p,t=%p,p=%p$%b$%^h", + "v=16$", "v=19$", "", NULL, + RANGE(LIBAR2_MIN_M_COST, LIBAR2_MAX_M_COST), + RANGE(LIBAR2_MIN_T_COST, LIBAR2_MAX_T_COST), + RANGE(LIBAR2_MIN_LANES, LIBAR2_MAX_LANES), + RANGE(LIBAR2_MIN_SALTLEN, LIBAR2_MAX_SALTLEN), BASE64, + &hashsize, RANGE(LIBAR2_MIN_HASHLEN, LIBAR2_MAX_HASHLEN), BASE64); + if (!r) + return 0; + + /* Return hash size */ + if (!hashsize) + hashsize = argon2__HASH_SIZE; + *len_out = (size_t)hashsize; + + /* Check password size */ +#if SIZE_MAX > UINT32_MAX + return len <= UINT32_MAX; +#else + (void) len; + return 1; +#endif +} + + +#else + + +CONST int +main(void) +{ + return 0; +} + + +#endif +/* TODO test */ diff --git a/common.h b/common.h index 389244f..53f4ff4 100644 --- a/common.h +++ b/common.h @@ -4,22 +4,36 @@ # include # include #endif +#include #include #include #include +#include +#include #include #include #include -#include "algorithms.h" + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunsafe-buffer-usage" /* completely broken */ +# pragma clang diagnostic ignored "-Wpadded" /* don't care */ +# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" /* glibc issue */ +# pragma clang diagnostic ignored "-Wc11-extensions" /* glibc issue */ +# pragma clang diagnostic ignored "-Wunknown-warning-option" /* ignoring -Wsuggest-attribute=const|pure */ +# pragma clang diagnostic ignored "-Wimplicit-void-ptr-cast" /* C++ warning, and we are in internal files */ +# pragma clang diagnostic ignored "-Wc++-keyword" /* C++ warning, and we are in internal files */ +#endif #if defined(__GNUC__) # define HIDDEN __attribute__((__visibility__("hidden"))) # define CONST __attribute__((__const__)) +# define PURE __attribute__((__pure__)) #else # define HIDDEN # define CONST +# define PURE #endif #define NONSTRING @@ -45,6 +59,9 @@ #define ELEMSOF(ARRAY) (sizeof(ARRAY) / sizeof(*(ARRAY))) +#include "algorithms.h" + + /** * Which function `librecrypt_hash_` shall behave as */ @@ -76,20 +93,6 @@ enum action { * Hash algorithm information and implementation */ struct algorithm { - /** - * Get the number of bytes that constitute - * the algorithm specification and configuration - * - * @param settings The password hash string, containing a single algorithm - * @param len The number of bytes in `settings` - * @return `len` if the string does not contain any hash result, - * otherwise the offset of `settings` where the hash - * result begins - * - * This function shall be MT-Safe and AS-Safe - */ - size_t (*get_prefix)(const char *settings, size_t len); - /** * Determine if a password hash string * selects the algorithm @@ -145,12 +148,14 @@ struct algorithm { * iff non-zero; ignored if `phrase` is non-`NULL` * @param settings The password hash string; it is allowed for algorithm * tuning parameters, and the hash result, to be omitted + * @param prefix The number of bytes in `settings` + * @param len_out Output parameter for the binary hash size, in bytes * @return 1 if the configuration is supported and correctly * configured, 0 otherwise * * This function shall be MT-Safe and AS-Safe */ - int (*test_supported)(const char *phrase, size_t len, int text, const char *settings, size_t prefix); + int (*test_supported)(const char *phrase, size_t len, int text, const char *settings, size_t prefix, size_t *len_out); /** * See `librecrypt_make_settings` @@ -190,11 +195,17 @@ struct algorithm { */ size_t hash_size; + /** + * 1 if `.hash_size` is just a default, + * 0 if `.hash_size` is always used + */ + signed char flexible_hash_size; + /** * Expected argument for the `strict_pad` parameter * of the `librecrypt_decode` function */ - int strict_pad; + signed char strict_pad; /** * Expected argument for the `pad` parameter @@ -207,12 +218,24 @@ struct algorithm { * Check if an `struct algorithm *` is the `END_OF_ALGORITHMS` * at the end of `librecrypt_algorithms_` */ -#define IS_END_OF_ALGORITHMS(A) (!(A)->get_prefix) +#define IS_END_OF_ALGORITHMS(A) (!(A)->is_algorithm) /** * Used at the end of `librecrypt_algorithms_` */ -#define END_OF_ALGORITHMS {NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, '\0'} +#define END_OF_ALGORITHMS\ + {\ + .is_algorithm = NULL,\ + .hash = NULL,\ + .test_supported = NULL,\ + .make_settings = NULL,\ + .encoding_lut = NULL,\ + .decoding_lut = NULL,\ + .hash_size = 0u,\ + .flexible_hash_size = 0,\ + .strict_pad = 0,\ + .pad = '\0'\ + } /** * Create a concatenation of `ALPHABET` repeated @@ -354,6 +377,58 @@ LIBRECRYPT_READ_MEM__(1, 2) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ HIDDEN const struct algorithm *librecrypt_find_first_algorithm_(const char *settings, size_t len); +/** + * Function used to validate a password hash string + * + * @param settings The string to validate + * @param len The number of bytes in `settings` + * @param fmt The expected format of the string, it may contain + * the metacharacter '%' (`fmt` is parsed left to right) + * to perform special string content checks: + * "%%" - Literal '%' + * "%*" - Any sequence of non-'$' bytes (greedly matched) + * "%s" - String + * "%u" - Unsigned integer that may start with a leading zeroes + * "%p" - Unsigned integer that most not start with a leading zeroes + * "%b" - Binary data, either encoded to ASCII or ungenerated content + * that is length specified using asterisk-notation + * "%h" - Same as "%b", except empty content as always allowed unless + * asterisk-notation is used + * "%^s" - Same as "%^s" except with output argument + * "%^u" - Same as "%^s" except with output argument + * "%^p" - Same as "%^s" except with output argument + * "%^b" - Same as "%^s" except with output argument + * "%^h" - Same as "%^s" except with output argument + * @param ... Arguments for each use of '%' in `fmt`: + * "%%" - None + * "%*" - None + * "%s" - At least one `const char *`: allowed matches (in order of preference), + * followed by a `NULL` + * "%u" - Two `uintmax_t`: the minimum value and the maximum value + * "%p" - Same as "%u" + * "%b" - Two `uintmax_t`: the minimum value and the maximum value, + * one `const unsigned char dlut[static 256]`: the ASCII-encoding decoding table, + * one `char`: the padding character or `'\0'` if none may be used, and + * one `int`: whether padding must always be used unless the previous argument is `'\0'` + * "%h" - Same as "%b" + * "%^s" - Same as "%s" but with an additional argument, as the first one: + * a `const char **` used to store the matched string + * "%^u" - Same as "%u" but with an additional argument, as the first one: + * a `uintmax_t *` used to store the encoded integer + * "%^p" - Same as "%p" but with an additional argument, as the first one: + * a `uintmax_t *` used to store the encoded integer + * "%^b" - Same as "%b" but with an additional argument, as the first one: + * a `uintmax_t *` used to store the number of encoded bytes or + * the encoded integer after the asterisk if asterisk-encoding is used + * "%^h" - Same as "%h" but with an additional argument, as the first one: + * a `uintmax_t *` used to store the number of encoded bytes or + * the encoded integer after the asterisk if asterisk-encoding is used + * @return 1 if `string` matches `fmt`, 0 otherwise + */ +LIBRECRYPT_READ_MEM__(1, 2) LIBRECRYPT_NONNULL_I__(3) LIBRECRYPT_WUR__ HIDDEN +int librecrypt_check_settings_(const char *settings, size_t len, const char *fmt, ...); + + #ifdef TEST # ifdef __linux__ @@ -364,7 +439,6 @@ const struct algorithm *librecrypt_find_first_algorithm_(const char *settings, s # include # include # include -# include # include # include diff --git a/config-coverage-gcc.mk b/config-coverage-gcc.mk new file mode 100644 index 0000000..6b9d9bd --- /dev/null +++ b/config-coverage-gcc.mk @@ -0,0 +1,11 @@ +CONFIGFILE_PROPER = config.mk +include $(CONFIGFILE_PROPER) + +CC = $(CC_PREFIX)gcc -std=c99 +GCOV = gcov + +CFLAGS = -g -O0 -pedantic -fprofile-arcs -ftest-coverage +LDFLAGS += -lgcov -fprofile-arcs + +coverage: check + $(GCOV) -pr $(SRC) 2>&1 diff --git a/librecrypt.h b/librecrypt.h index 9cf966b..726bfe5 100644 --- a/librecrypt.h +++ b/librecrypt.h @@ -13,15 +13,18 @@ # define LIBRECRYPT_NONNULL__ __attribute__((__nonnull__)) # define LIBRECRYPT_NONNULL_I__(I) __attribute__((__nonnull__(I))) # define LIBRECRYPT_WUR__ __attribute__((__warn_unused_result__)) -# define LIBRECRYPT_READ_STR__(S) __attribute__((__access__(read_only, S))) -# define LIBRECRYPT_READ_MEM__(B, N) __attribute__((__access__(read_only, B, N))) -# define LIBRECRYPT_WRITE_MEM__(B, N) __attribute__((__access__(write_only, B, N))) -# define LIBRECRYPT_READ_WRITE_STR__(S) __attribute__((__access__(read_write, S))) #else # define LIBRECRYPT_PURE__ # define LIBRECRYPT_NONNULL__ # define LIBRECRYPT_NONNULL_I__(I) # define LIBRECRYPT_WUR__ +#endif +#if defined(__GNUC__) && !defined(__clang__) +# define LIBRECRYPT_READ_STR__(S) __attribute__((__access__(read_only, S))) +# define LIBRECRYPT_READ_MEM__(B, N) __attribute__((__access__(read_only, B, N))) +# define LIBRECRYPT_WRITE_MEM__(B, N) __attribute__((__access__(write_only, B, N))) +# define LIBRECRYPT_READ_WRITE_STR__(S) __attribute__((__access__(read_write, S))) +#else # define LIBRECRYPT_READ_STR__(S) # define LIBRECRYPT_READ_MEM__(B, N) # define LIBRECRYPT_WRITE_MEM__(B, N) @@ -29,6 +32,14 @@ #endif #define LIBRECRYPT_NONNULL_1__ LIBRECRYPT_NONNULL_I__(1) +/* Silence clang's warnings for glibc */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +# pragma clang diagnostic ignored "-Wc11-extensions" +# pragma clang diagnostic ignored "-Wunsafe-buffer-usage" /* as well as this one, which is completely broken */ +#endif + /** * Symbol used as a general delimiter @@ -46,27 +57,36 @@ * Get number of bytes in a password hash string * that make up the algorithm configuration * - * @param hash The password hash string; must not be `NULL` - * @return The number of bytes, from the front of `hash`, - * that make up the algorithm configuration; may be 0 + * Some algorithms have flexible hash size which + * is encoded either with an actual hash (its + * length after decoding to binary is checked) + * or using asterisk-notation in place of the + * result (that is, as '*' followed by an unsigned, + * decimal integer, which may have leading zeroes). + * This part is always excluded (from the last + * algorithm in the algorithm chain in `hash`) in + * the return value. + * + * @param hash The password hash string; must not be `NULL` + * @param hashsize_out Unless `NULL`; if the hash size is fixed, + * the value 0 is stored in the provided memory, + * otherwise a non-zero value will be stored, + * which is the number bytes in the output hash, + * however if the a hash or hash size is not + * available (in which case the function shall + * return `strlen(hash)` if `hash` is properly + * formatted) the value 0 is stored, indicating + * that a default hash size shall be used + * @return The number of bytes, from the front of `hash`, + * that make up the algorithm configuration; may be 0 * * For the return value `r`, `&hash[r]` points to the * hash result proper * * This function is MT-Safe and AS-Safe */ -LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ LIBRECRYPT_PURE__ -inline size_t -librecrypt_settings_prefix(const char *hash) -{ - size_t i, ret = 0u; - - for (i = 0u; hash[i]; i++) - if (hash[i] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER || hash[i] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) - ret = i + 1u; - - return ret; -} +LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL_1__ LIBRECRYPT_WUR__ LIBRECRYPT_PURE__ +size_t librecrypt_settings_prefix(const char *hash, size_t *hashsize_out); /** @@ -264,9 +284,9 @@ size_t librecrypt_encode(char *out_buffer, size_t size, const void *binary, size /** - * Encode a hash result or salt in ASCII, from binary + * Decode a hash result or salt, from ASCII to binary * - * This is the inverse of `librecrypt_decode` + * This is the inverse of `librecrypt_encode` * * @param out_buffer Output buffer for the raw binary data * @param size The number bytes the function may write to `out_buffer` @@ -280,7 +300,8 @@ size_t librecrypt_encode(char *out_buffer, size_t size, const void *binary, size * @param strict_pad Zero if the padding at the end is optional, non-zero otherwise * @return The number of bytes that would have been written to `out_buffer`, * if `size` was sufficiently large, -1 on failure - * @throws EINVAL `ascii` uses an invalid encoding + * + * @throws EINVAL `ascii` uses an invalid encoding * * On successful completion, the N bytes is written to * `out_buffer` where N is the lesser of `size` and the @@ -319,8 +340,9 @@ ssize_t librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size * value of that character in the encoding alphabet, * and any other character to the value `0xFF`; * but `NULL` on failure - * @throws ENOSYS The last algorithm in `settings` is not recognised - * or was disabled at compile-time + * + * @throws ENOSYS The last algorithm in `settings` is not recognised + * or was disabled at compile-time * * The return type is `const char *` if `decoding` is 0, * and `const unsigned char *` otherwise @@ -419,7 +441,8 @@ librecrypt_equal(const char *a, const char *b) /** * Locate all asteriskes followed by a non-negative * decimal number and replace each with ASCII-encded - * random bytes (as many bytes as the number specifies) + * random bytes (as many bytes as the number specifies), + * except those used to specify the desired hash size * * @param out_buffer Output buffer for the ASCII representation * @param size The number bytes the function may write to `out_buffer` @@ -435,7 +458,6 @@ librecrypt_equal(const char *a, const char *b) * @return The number of bytes that would have been written to `out_buffer`, * if `size` was sufficiently large, -1 on failure * @throws ERANGE The expected return value is greater than {SSIZE_MAX} - * @throws EINVAL Asterisk-encoding was used in an invalid context * @throws ENOSYS `settings` contain an algorithm that is not recognised * or was disabled at compile-time * @@ -460,7 +482,7 @@ librecrypt_equal(const char *a, const char *b) * On failure, `out_buffer` may be partially written * * The encoding depend on the algorithm, which is why - * it can fail with `EINVAL` or `ENOSYS` + * it can fail with `ENOSYS` * * When `rng` is non-`NULL`, this function inherits any * MT-Unsafe and AS-Unsafe properties from `*rng`, being @@ -503,11 +525,12 @@ ssize_t librecrypt_realise_salts(char *restrict out_buffer, size_t size, const c * purposes, ignored if `rng` is `NULL` or if `gensalt` is zero * @return The number of bytes that would have been written to * `out_buffer`, if `size` was sufficiently large, -1 on failure - * @throws EINVAL `algorithm` represents a chain of algorithms - * @throws ENOSYS `algorithm` represents an algorithm that is not - * recognised or was disabled at compile-time - * @throws ENOSYS `algorithm` is `NULL` but all algorithms were disabled at - * compile-time + * + * @throws EINVAL `algorithm` represents a chain of algorithms + * @throws ENOSYS `algorithm` represents an algorithm that is not + * recognised or was disabled at compile-time + * @throws ENOSYS `algorithm` is `NULL` but all algorithms were disabled at + * compile-time * * If `rng` is `NULL`, any encountered `EINTR` is ignored, * however, if it is encountered `errno` will be set to `EINTR`, @@ -728,6 +751,16 @@ int librecrypt_test_supported(const char *phrase, size_t len, int text, const ch /** * Chain togather another set of hash algorithms * + * If you are using the `librecrypt_crypt` format, + * you just run this function over the password hash + * string to get the augmented one with an updated + * hash, however if you are using `librecrypt_hash` + * or `librecrypt_hash_binary`, you must also (since + * `augend` would not contain the hash result) run + * `librecrypt_hash` or `librecrypt_hash_binary` over + * the old hash result with `augment` as the `settings` + * argument to get the new hash result + * * @param out_buffer Output buffer for the new password hash string * @param size The number bytes the function may write to `out_buffer` * @param augend THe existing password hash string; if it contains a @@ -773,4 +806,8 @@ ssize_t librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend, const char *restrict augment, void *reserved); +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + #endif diff --git a/librecrypt_add_algorithm.3 b/librecrypt_add_algorithm.3 index 77867e8..0bb6566 100644 --- a/librecrypt_add_algorithm.3 +++ b/librecrypt_add_algorithm.3 @@ -121,6 +121,29 @@ T} Async-signal safety AS-Unsafe .TE .sp +.SH EXTENDED DESCRIPTION +If you are using the +.BR librecrypt_crypt (3) +format, you just run the +.BR librecrypt_add_algorithm () +function over the password hash string to get +the augmented password hash string with an +updated hash, however if you are using +.BR librecrypt_hash (3) +or +.BR librecrypt_hash_binary (3), +you must also (since +.I augend +would not contain the hash result) run +.BR librecrypt_hash (3) +or +.BR librecrypt_hash_binary (3) +over the old hash result with +.I augment +as the +.I settings +argument to get the new hash result. + .SH HISTORY The .BR librecrypt_add_algorithm () diff --git a/librecrypt_add_algorithm.c b/librecrypt_add_algorithm.c index a2d8aa4..167713f 100644 --- a/librecrypt_add_algorithm.c +++ b/librecrypt_add_algorithm.c @@ -3,51 +3,109 @@ #ifndef TEST +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wformat-truncation=" /* we rely on snprintf(3) doing truncation */ +#endif + + ssize_t librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend, const char *restrict augment, void *reserved) { - size_t prefix1 = librecrypt_settings_prefix(augend); - size_t prefix2, min, ret, len, phraselen; + size_t prefix1, prefix2, min, ret, len, phraselen; + size_t hashsize1, hashsize2; char *phrase, pad; - int strict_pad; + int strict_pad, r_int, nul_term; const unsigned char *lut; ssize_t r; #define COPY_PREFIX()\ do {\ - size -= 1u;\ min = prefix1 < size ? prefix1 : size;\ if (out_buffer != augend)\ memmove(out_buffer, augend, min);\ out_buffer = &out_buffer[min];\ size -= min;\ - if (size) {\ - *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER;\ - size -= 1u;\ - }\ } while (0) + /* Reserve space for NUL-termination */ + if (size) { + nul_term = 1; + size -= 1u; + } else { + nul_term = 0; + } + + /* Get the prefix and hash size in `augend` and `augment` */ + prefix1 = librecrypt_settings_prefix(augend, &hashsize1); + prefix2 = librecrypt_settings_prefix(augment, &hashsize2); + + /* If `augend` specifies as hash size rather than a hash, include it as the prefix */ + if (augend[prefix1] == '*') { + prefix1 += strlen(&augend[prefix1]); + hashsize1 = 0u; + } + + /* If `augend` doesn't contain a hash, we do not need to hash the hash, + * but we do need to include the final hash size if it is configurable */ if (!augend[prefix1]) { - prefix2 = librecrypt_settings_prefix(augment); + if (augment[prefix2] == '*') { + prefix2 += strlen(&augment[prefix2]); + hashsize2 = 0u; + } ret = prefix1 + 1u + prefix2; if (size) { COPY_PREFIX(); + if (size) { + *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER; + size -= 1u; + } min = prefix2 < size ? prefix2 : size; memcpy(out_buffer, augment, min); - out_buffer[min] = '\0'; + if (hashsize2) { + r_int = snprintf(&out_buffer[min], size + 1u, "*%zu", hashsize2); + if (r_int < 2) + abort(); + ret += (size_t)r_int; + } else { + out_buffer[min] = '\0'; + } + } else { + r_int = snprintf(NULL, 0, "*%zu", hashsize2); + if (r_int < 2) + abort(); + ret += (size_t)r_int; } return (ssize_t)ret; } - if (size <= prefix1 + 2u) { + /* Measure size of hash size specification for `augend` */ + if (hashsize1) { + r_int = snprintf(NULL, 0, "*%zu", hashsize1); + if (r_int < 2) + abort(); + } else { + r_int = 0; + } + + /* Measure `augent` and '>' in output */ + ret = prefix1 + (size_t)r_int + 1u; + + /* Decode the hash from base-64 to binary */ + if (size <= ret + prefix2 + 1u) { + /* If the new hash doesn't fit, don't bother; + * hash sizes are independent of password size */ phrase = NULL; phraselen = 0u; } else { - lut = librecrypt_get_encoding(augend, strlen(augend), &pad, &strict_pad, 1); + /* Measure old ASCII hash; `strlen(augent)` will be `prefix1 + len` */ + len = strlen(&augend[prefix1]); + + /* Get encoding information */ + lut = librecrypt_get_encoding(augend, prefix1 + len, &pad, &strict_pad, 1); if (!lut) return -1; - len = strlen(&augend[prefix1]); + /* Measure old binary hash */ r = librecrypt_decode(NULL, 0, &augend[prefix1], len, lut, pad, strict_pad); if (r <= 0) { if (!r) @@ -55,6 +113,8 @@ librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend, const char return -1; } phraselen = (size_t)r; + + /* Decode old hash from ASCII to binary */ phrase = malloc(phraselen); if (!phrase) return -1; @@ -62,9 +122,20 @@ librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend, const char abort(); } + /* Chain the hash algorithms: write `augent` */ COPY_PREFIX(); - ret = prefix1 + 1u; - r = librecrypt_crypt(out_buffer, size + 1u, phrase, phraselen, augment, reserved); + if (hashsize1 && size) + if (snprintf(out_buffer, size + 1u, "*%zu", hashsize1) != r_int) + abort(); + + /* Chain the hash algorithms: write '>' */ + if (size) { + *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER; + size -= 1u; + } + + /* Chain the hash algorithms: write `augment` and hash */ + r = librecrypt_crypt(out_buffer, size + (nul_term ? 1u : 0u), phrase, phraselen, augment, reserved); if (r <= 0) { librecrypt_wipe(phrase, phraselen); free(phrase); diff --git a/librecrypt_algorithms_.c b/librecrypt_algorithms_.c index 34c6b3f..0213dff 100644 --- a/librecrypt_algorithms_.c +++ b/librecrypt_algorithms_.c @@ -5,14 +5,14 @@ #define ENTRY(ALGO)\ {\ - .get_prefix = &librecrypt__##ALGO##__get_prefix,\ .is_algorithm = &librecrypt__##ALGO##__is_algorithm,\ .hash = librecrypt__##ALGO##__hash,\ .test_supported = &librecrypt__##ALGO##__test_supported,\ .make_settings = &librecrypt__##ALGO##__make_settings,\ - .encoding_lut = &librecrypt__##ALGO##__encoding_lut,\ - .decoding_lut = &librecrypt__##ALGO##__decoding_lut,\ + .encoding_lut = librecrypt__##ALGO##__encoding_lut,\ + .decoding_lut = librecrypt__##ALGO##__decoding_lut,\ .hash_size = ALGO##__HASH_SIZE,\ + .flexible_hash_size = ALGO##__FLEXIBLE_HASH_SIZE,\ .strict_pad = ALGO##__STRICT_PAD,\ .pad = ALGO##__PAD\ } @@ -39,28 +39,55 @@ main(void) SET_UP_ALARM(); + /* Validate algorithm entry */ for (i = 0u; i < count; i++) { - CHECK(!IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[i])); - CHECK(librecrypt_algorithms_[i].get_prefix != NULL); - CHECK(librecrypt_algorithms_[i].is_algorithm != NULL); - CHECK(librecrypt_algorithms_[i].hash != NULL); - CHECK(librecrypt_algorithms_[i].test_supported != NULL); - CHECK(librecrypt_algorithms_[i].make_settings != NULL); - CHECK(librecrypt_algorithms_[i].encoding_lut != NULL); - CHECK(librecrypt_algorithms_[i].decoding_lut != NULL); - CHECK(librecrypt_algorithms_[i].hash_size > 0u); - CHECK(librecrypt_algorithms_[i].strict_pad >= 0); - CHECK(librecrypt_algorithms_[i].strict_pad <= 1); + /* Stop at end-of-list marker */ + EXPECT(!IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[i])); + + /* Check all functions are set */ + EXPECT(librecrypt_algorithms_[i].is_algorithm != NULL); + EXPECT(librecrypt_algorithms_[i].hash != NULL); + EXPECT(librecrypt_algorithms_[i].test_supported != NULL); + EXPECT(librecrypt_algorithms_[i].make_settings != NULL); + + /* Check the salt and hash encoding tables are set */ + EXPECT(librecrypt_algorithms_[i].encoding_lut != NULL); + EXPECT(librecrypt_algorithms_[i].decoding_lut != NULL); + + /* Check .flexible_hash_size is either 0 and 1 */ + EXPECT(librecrypt_algorithms_[i].flexible_hash_size >= 0); + EXPECT(librecrypt_algorithms_[i].flexible_hash_size <= 1); + + /* Check hash_size is set if .flexible_hash_size is 0 */ + if (!librecrypt_algorithms_[i].flexible_hash_size) + EXPECT(librecrypt_algorithms_[i].hash_size > 0u); + + /* Check .strict_pad is either 0 and 1 */ + EXPECT(librecrypt_algorithms_[i].strict_pad >= 0); + EXPECT(librecrypt_algorithms_[i].strict_pad <= 1); + + /* Check .pad is NUL (none) or valid */ if (librecrypt_algorithms_[i].pad) { - CHECK(librecrypt_algorithms_[i].pad > ' '); - CHECK(librecrypt_algorithms_[i].pad < 0x7F); - CHECK(librecrypt_algorithms_[i].pad != LIBRECRYPT_HASH_COMPOSITION_DELIMITER); - CHECK(librecrypt_algorithms_[i].pad != LIBRECRYPT_ALGORITHM_LINK_DELIMITER); - CHECK(librecrypt_algorithms_[i].pad != '*'); + /* printable but not whitespace */ + EXPECT(librecrypt_algorithms_[i].pad > ' '); + EXPECT(librecrypt_algorithms_[i].pad < 0x7F); + /* does not have special meaning */ + EXPECT(librecrypt_algorithms_[i].pad != LIBRECRYPT_HASH_COMPOSITION_DELIMITER); + EXPECT(librecrypt_algorithms_[i].pad != LIBRECRYPT_ALGORITHM_LINK_DELIMITER); + EXPECT(librecrypt_algorithms_[i].pad != '*'); + /* is not ':' which has special meaning in table files */ + EXPECT(librecrypt_algorithms_[i].pad != ':'); + /* other characters forbidden by crypt(5) */ + EXPECT(librecrypt_algorithms_[i].pad != '!'); + EXPECT(librecrypt_algorithms_[i].pad != ';'); + EXPECT(librecrypt_algorithms_[i].pad != '\\'); } } + + /* Check number of algorithms in the list was the number of supported algorithms */ assert(i == count); - CHECK(IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[count])); + /* Check that the list has an end-of-list marker */ + EXPECT(IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[count])); return 0; } diff --git a/librecrypt_chain_length.c b/librecrypt_chain_length.c index 64b9472..2ce6cf2 100644 --- a/librecrypt_chain_length.c +++ b/librecrypt_chain_length.c @@ -13,6 +13,8 @@ int main(void) { SET_UP_ALARM(); + + /* Check returns number of '>' plus 1 */ EXPECT(librecrypt_chain_length("") == 1u); EXPECT(librecrypt_chain_length("a$b") == 1u); EXPECT(librecrypt_chain_length(">") == 2u); @@ -20,6 +22,7 @@ main(void) EXPECT(librecrypt_chain_length("a$b>c$d>e$f") == 3u); EXPECT(librecrypt_chain_length("a$b>c$d>e$f>") == 4u); EXPECT(librecrypt_chain_length(">a$b>c$d>e$f>") == 5u); + return 0; } diff --git a/librecrypt_check_settings_.c b/librecrypt_check_settings_.c new file mode 100644 index 0000000..9140120 --- /dev/null +++ b/librecrypt_check_settings_.c @@ -0,0 +1,255 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +/** + * Parse and validate an unsigned, decimal integer + * + * @param settings The password hash string + * @param off The current in `settings` where the integer + * begins; will be updated to its ends (one byte + * after the last character encoding the integer) + * @param len The number of bytes in `settings` + * @param min_first_digit '0' if leading zeroes are permitted, '1' otherwise + * @param min The smallest legal value + * @param max The greatest legal value + * @param out Output parameter for the encoded integer, or `NULL` + * @return 1 if a valid integer was encoded, 0 otherwise + */ +static int +check_uint(const char *settings, size_t *off, size_t len, char min_first_digit, + uintmax_t min, uintmax_t max, uintmax_t *out) +{ + uintmax_t digit, value; + + /* Reject the empty string */ + if (*off >= len) + return 0; + + /* Reject leading zeroes unless `min_first_digit` is '0' */ + if (min_first_digit > settings[*off] || settings[*off] > '9') { + /* With leading zeroes being rejected, a single '0' may be + * parsed as the value 0 (if that value is permitted) */ + if (min == 0u && min_first_digit != '0' && settings[*off] == '0') { + ++*off; + if (out) + *out = 0u; + return 1; + } + return 0; + } + + /* Decode the integer */ + value = (uintmax_t)(settings[*off] - '0'); + ++*off; + while (*off < len && '0' <= settings[*off] && settings[*off] <= '9') { + digit = (uintmax_t)(settings[*off] - '0'); + if (value > (UINTMAX_MAX - digit) / 10u) + return 0; + value *= 10u; + value += digit; + ++*off; + } + if (out) + *out = value; + + /* Check that the integer is within the accepted range */ + return min <= value && value >= max; +} + + +/** + * Validate a salt or hash that's either encoded in base-64 + * or has its length encoded using asterisk-notation + * + * This function does not check the value of any excess bit + * in the base-64 encoding + * + * @param settings The password hash string + * @param off The current in `settings` where the integer + * begins; will be updated to its ends (one byte + * after the last character encoding the integer) + * @param len The number of bytes in `settings` + * @param min The least allowed number of bytes + * @param max The most allowed number of bytes + * @param allow_empty Whether the empty string is allowed + * (no encoded bytes and no asterisk-notation) + * @param lut Alphabet reverse lookup table, shall map any valid + * character (except the padding character) to the value + * of that character in the encoding alphabet, and map + * any other character to the value `0xFF` + * @param pad The padding character to used at the end; the NUL byte if none + * @param strict_pad Zero if the padding at the end is optional, non-zero otherwise + * @param out Output parameter for the number of bytes used by the salt or hash + * @return 1 if the encoded value was of proper length, + * a proper length was encoded using asterisk-notation, or + * if `allow_empty` was non-zero, nothing was encoded; + * 0 otherwise + */ +static int +check_data(const char *settings, size_t *off, size_t len, uintmax_t min, uintmax_t max, int allow_empty, + const unsigned char dlut[restrict static 256], char pad, int strict_pad, uintmax_t *out) +{ + size_t i, old_i; + uintmax_t q, r, n; + + /* Check for asterisk-notation */ + if (*off < len && settings[*off] == '*') { + ++*off; + return check_uint(settings, off, len, '0', min, max, out); + } + + settings = &settings[*off]; + len -= *off; + i = 0u; + + /* Get number of base-64 characters available, excluding padding */ + while (i < len && dlut[(unsigned char)settings[i]] < 64u) + i++; + + /* Calculate number of encoded bytes */ + q = i / 4u; + r = i % 4u; + n = q * 3u + r - (r ? 1u : 0u); + if (out) + *out = n; + /* 1 base-64 character in excees of a multiple of 4, + * this is illegal because 4 characters encode 3 bytes + * without any excees bits (both are 24 bits), so + * if the number of encoded bytes is not a multiple + * of 3, at least 2 characters in excees of multiple + * of 4 is required because at least 2 characeters is + * required to encode 1 byte in base-64 */ + if (r == 1u) + return 0; + + /* Check number of bytes is within the specified range, + * but accept 0 if `allow_empty` is non-zero */ + if (min > n || n > max) + if (n || !allow_empty) + return 0; + + /* Check padding if allowed or even required */ + if (r && pad) { + old_i = i; + while (r < 4u && i < len && settings[i] == pad) { + i++; + r++; + } + if ((i != old_i || strict_pad) && r != 4u) + return 0; + } + + *off += i; + return 1; +} + + +int +librecrypt_check_settings_(const char *settings, size_t len, const char *fmt, ...) +{ + size_t i = 0u; + uintmax_t *uout, umin, umax; + const unsigned char *dlut; + char pad; + int strict_pad; + int output = 0; + va_list args; + const char *str, **strout; + size_t n; + + va_start(args, fmt); + + while (*fmt) { + if (*fmt != '%') { + /* Normal literal character */ + if (i == len || settings[i++] != *fmt++) + return 0; + + } else if (fmt[1u] == '%') { + /* '%'-escaped literal '%' ("%%") */ + if (i == len || settings[i++] != '%') + return 0; + fmt = &fmt[2u]; + + } else if (fmt[1u] == '*') { + /* "%*": skip to next '$' or the end of no '$' was found */ + while (i < len && settings[i] != '$') + i++; + fmt = &fmt[2u]; + + } else if (fmt[1u] == '^') { + /* '^' inserted between '%' and some letter may be used + * have the value returned */ + output = 1; + fmt++; + goto outable; + + } else outable: if (fmt[1u] == 'p' || fmt[1u] == 'u') { + /* Unsigned integers ("%p" for leading zeros forbidden, "%u" for leading zeros allowed) */ + uout = output ? va_arg(args, uintmax_t *) : NULL; + umin = va_arg(args, uintmax_t); + umax = va_arg(args, uintmax_t); + if (!check_uint(settings, &i, len, fmt[1u] == 'p' ? '1' : '0', umin, umax, uout)) + return 0; + goto outable_done; + + } else if (fmt[1u] == 'd' || fmt[1u] == 'h') { + /* Base-64 or asterisk-notation ("%d" for normal, "%h" for "" allowed) */ + uout = output ? va_arg(args, uintmax_t *) : NULL; + umin = va_arg(args, uintmax_t); + umax = va_arg(args, uintmax_t); + dlut = va_arg(args, const unsigned char *); + pad = (char)va_arg(args, int); /* `char` is promoted to `int` when passed through `...` */ + strict_pad = va_arg(args, int); + if (!check_data(settings, &i, len, umin, umax, fmt[1u] == 'h', dlut, pad, strict_pad, uout)) + return 0; + goto outable_done; + + } else if (fmt[1u] == 's') { + /* String from fixed set ("%s") */ + strout = output ? va_arg(args, const char **) : NULL; + for (;;) { + str = va_arg(args, const char *); + if (!str) + return 0; + n = strlen(str); + if (n <= len - i && !strncmp(&settings[i], str, n)) { + if (strout) + *strout = str; + i += n; + break; + } + } + while ((str = va_arg(args, const char *))); + goto outable_done; + + } else { + abort(); + + outable_done: + output = 0; + fmt = &fmt[2u]; + } + } + + va_end(args); + + return i == len; +} + + + +#else + + +CONST int +main(void) +{ + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_common_rfc4848s4_decoding_lut_.c b/librecrypt_common_rfc4848s4_decoding_lut_.c new file mode 100644 index 0000000..457be19 --- /dev/null +++ b/librecrypt_common_rfc4848s4_decoding_lut_.c @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +const unsigned char librecrypt_common_rfc4848s4_decoding_lut_[256u] = { + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, 62, XX, XX, XX, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, XX, XX, XX, XX, XX, XX, + XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, XX, XX, XX, XX, XX, + XX, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX +}; + + +#else + + +int +main(void) +{ + unsigned i, found[64u], invalids = 0u; + + SET_UP_ALARM(); + + /* Ensure values [0, 64) are encoded exactly once each, + * and that all other characters are marked as invalid */ + memset(found, 0, sizeof(found)); + for (i = 0u; i < 256u; i++) { + if (librecrypt_common_rfc4848s4_decoding_lut_[i] == 0xFFu) { + invalids += 1u; + } else { + EXPECT(librecrypt_common_rfc4848s4_decoding_lut_[i] < 64u); + found[librecrypt_common_rfc4848s4_decoding_lut_[i]] += 1u; + } + } + EXPECT(invalids == 256u - 64u); + for (i = 0u; i < 64u; i++) + EXPECT(found[i] == 1u); + + /* Match with librecrypt_common_rfc4848s4_encoding_lut_ is + * tested in librecrypt_common_rfc4848s4_decoding_lut_.c */ + + return 0; +} + + +#endif diff --git a/librecrypt_common_rfc4848s4_encoding_lut_.c b/librecrypt_common_rfc4848s4_encoding_lut_.c new file mode 100644 index 0000000..89b2feb --- /dev/null +++ b/librecrypt_common_rfc4848s4_encoding_lut_.c @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#define ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +NONSTRING const char librecrypt_common_rfc4848s4_encoding_lut_[256u] = MAKE_ENCODING_LUT(ALPHABET); + + +#else + + +int +main(void) +{ + size_t i; + char c; + + SET_UP_ALARM(); + + for (i = 0u; i < 64u; i++) { + c = librecrypt_common_rfc4848s4_encoding_lut_[i]; + + /* Check alphabet repeated 4 times (64 characters become 256) */ + EXPECT(librecrypt_common_rfc4848s4_encoding_lut_[i + 64u * 1u] == c); + EXPECT(librecrypt_common_rfc4848s4_encoding_lut_[i + 64u * 2u] == c); + EXPECT(librecrypt_common_rfc4848s4_encoding_lut_[i + 64u * 3u] == c); + + /* Check alphabet is valid: */ + /* printable but not whitespace */ + EXPECT(c > ' '); + EXPECT(c < '\x7F'); + /* character does not have special mean */ + EXPECT(c != LIBRECRYPT_HASH_COMPOSITION_DELIMITER); + EXPECT(c != LIBRECRYPT_ALGORITHM_LINK_DELIMITER); + EXPECT(c != '*'); + /* character is not ':' which has special meaning in table files */ + EXPECT(c != ':'); + /* other characters forbidden by crypt(5) */ + EXPECT(c != ';'); + EXPECT(c != '!'); + EXPECT(c != '\\'); + + /* Check encoding LUT matches decoding LUT; + * this verifies that characters in the alphabet are unique */ + EXPECT(librecrypt_common_rfc4848s4_decoding_lut_[(unsigned char)c] == i); + } + + return 0; +} + + +#endif diff --git a/librecrypt_decode.c b/librecrypt_decode.c index ee451a7..847bfea 100644 --- a/librecrypt_decode.c +++ b/librecrypt_decode.c @@ -12,6 +12,15 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, unsigned char a, b, c, d; size_t n = 0u; + /* For as many multiple of 4 input characters as available, + * it can be coded into multiples of 3 bytes (or less if + * padding is used), however we need to check that the output + * buffer is large enough. Even if `size` is 0, we need read + * all characters to ensure that they only use the valid + * characters in the encoding alphabet or that padding is + * used correct — we want to validate the string, and we do + * not support padding in the middle (it is technically + * doable, but there is no reason it would ever be used) */ for(; len >= 4u; str += 4u) { len -= 4u; @@ -44,16 +53,28 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, n++; } - if (len && strict_pad && pad) + /* If the number of input characters was not a multiple of + * four, the string is invalid if padding was required + * (`strict_pad` is ignored if `pad` is the NUL byte) */ + if (len && pad && strict_pad) goto einval; + /* Decode the remainder of the input, which shouldn't + * contain any padding since it is less than 4 bytes, + * and padding is done to multiples of 4 bytes */ switch (len) { case 0u: + /* 0 characters left over, there was nothing left, and we are done */ goto out; + case 1u: + /* 1 character left over, this is invalid as at least 2 base-64 + * characters is required to encode at least 1 byte */ goto einval; default: + /* 2 or 3 characters left over */ + a = lut[str[0u]]; b = lut[str[1u]]; if ((a | b) == 0xFFu) @@ -66,7 +87,9 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, c = lut[str[2u]]; if (c == 0xFFu) { - if (!pad || str[2u] != pad) + if (!pad) + goto einval; + if (str[2u] != pad) goto einval; break; } @@ -112,29 +135,41 @@ static const unsigned char lut[256u] = { check((BINARY), sizeof(BINARY) - 1u, (ASCII PAD), sizeof(ASCII) - 1u, sizeof(ASCII PAD) - 1u) +static int check_good_padding = 1; + + static void check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_len, size_t padded_len) { char buf[16u]; size_t i, j; + /* Internal test check */ assert(binary_len <= sizeof(buf)); - assert(padded_len % 4u == 0u); - assert(padded_len / 4u == (unpadded_len + 3u) / 4u); + /* Internal test check */ + if (check_good_padding) { + assert(padded_len % 4u == 0u); + assert(padded_len / 4u == (unpadded_len + 3u) / 4u); + } assert(padded_len >= unpadded_len); + /* Test no output and without padding */ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len); EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 1) == (ssize_t)binary_len); EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 0) == (ssize_t)binary_len); - if (padded_len == unpadded_len) { EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == (ssize_t)binary_len); } else { + /* illegal combination: padding missing but required */ errno = 0; EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == -1); EXPECT(errno == EINVAL); + } + /* Test no output and with padding */ + if (padded_len != unpadded_len) { + /* illegal combinations: padding present no padding character defined (padded with illegal characters) */ errno = 0; EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 0) == -1); EXPECT(errno == EINVAL); @@ -143,10 +178,11 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 1) == -1); EXPECT(errno == EINVAL); } - EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 0) == (ssize_t)binary_len); - EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); + if (check_good_padding) + EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); + /* Test output, with and without truncation */ for (i = 0u; i < sizeof(buf); i++) { memset(buf, 99, sizeof(buf)); EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len); @@ -197,12 +233,14 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_ for (; j < sizeof(buf); j++) EXPECT(buf[j] == 99); - memset(buf, 99, sizeof(buf)); - EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); - j = i < binary_len ? i : binary_len; - EXPECT(!memcmp(buf, binary, j)); - for (; j < sizeof(buf); j++) - EXPECT(buf[j] == 99); + if (check_good_padding) { + memset(buf, 99, sizeof(buf)); + EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); + j = i < binary_len ? i : binary_len; + EXPECT(!memcmp(buf, binary, j)); + for (; j < sizeof(buf); j++) + EXPECT(buf[j] == 99); + } } } @@ -215,6 +253,8 @@ main(void) SET_UP_ALARM(); + /* Normal test cases */ + check_good_padding = 1; CHECK("", "", ""); CHECK("\x00", "AA", "=="); CHECK("\x00\x00", "AAA", "="); @@ -224,6 +264,14 @@ main(void) CHECK("zy[]y21 !", "enlbXXkyMSAh", ""); CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw", "=="); + /* Bad but acceptable case */ + check_good_padding = 0; + CHECK("\x00", "AA", "="); + + /* Test illegal characters, and 1 non-value character with + * 3 pad characeters (illegal since at least 2 base-64 + * characters are needed to encode at least 1 byte) */ + check_good_padding = 1; for (i = 0; i <= 1; i++) { errno = 0; EXPECT(librecrypt_decode(NULL, 0u, "AA=*", 4u, lut, '=', i) == -1); @@ -248,6 +296,42 @@ main(void) errno = 0; EXPECT(librecrypt_decode(NULL, 0u, "====", 4u, lut, '=', i) == -1); EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "A*==", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "*A==", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "A*", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "*A", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "=", 1u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "==", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "=A", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA*", 3u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA*", 3u, lut, '\0', i) == -1); + EXPECT(errno == EINVAL); } return 0; diff --git a/librecrypt_decompose_chain.c b/librecrypt_decompose_chain.c index 9b7aede..9908a5e 100644 --- a/librecrypt_decompose_chain.c +++ b/librecrypt_decompose_chain.c @@ -44,6 +44,7 @@ main(void) SET_UP_ALARM(); + /* Check HASH_1 with different sizes of `chain` */ stpcpy(buf, HASH_1); EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 1u); EXPECT(!strcmp(buf, HASH_1)); @@ -56,6 +57,7 @@ main(void) EXPECT(!strcmp(buf, HASH_1)); EXPECT(chain[0u] == buf); + /* Check HASH_2 with different sizes of `chain` */ stpcpy(buf, HASH_2); EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 3u); EXPECT(!strcmp(buf, HASH_2)); @@ -81,6 +83,7 @@ main(void) EXPECT(!strcmp(chain[1u], HASH_2_B)); EXPECT(!strcmp(chain[2u], HASH_2_C)); + /* Check HASH_3 with different sizes of `chain` */ stpcpy(buf, HASH_3); EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 5u); EXPECT(!strcmp(buf, HASH_3)); diff --git a/librecrypt_decompose_chain1.c b/librecrypt_decompose_chain1.c index 718e54c..216c649 100644 --- a/librecrypt_decompose_chain1.c +++ b/librecrypt_decompose_chain1.c @@ -26,10 +26,15 @@ main(void) { char buf[64u]; size_t n; + SET_UP_ALARM(); + + /* Check each '>' was replaced with NUL, and number + * of '>' plus 1 (number of algorithms) was returned */ CHECK("", "", 1u); CHECK(">", "\0", 2u); CHECK("a$b>c$d>e$f", "a$b\0c$d\0e$f", 3u); + return 0; } diff --git a/librecrypt_encode.c b/librecrypt_encode.c index 2199ac1..51a0a2e 100644 --- a/librecrypt_encode.c +++ b/librecrypt_encode.c @@ -22,16 +22,29 @@ librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len, unsigned char a, b, c; size_t q, r, i, j, n; + /* 1 byte (8 bits) requires 2 base-64 characters (12 bits), + * 2 bytes (16 bits) requires 3 base-64 characters (18 bits), and + * 3 bytes (24 bits) requires 4 base-64 characters (24 bits) exactly */ q = len / 3u; r = len % 3u; n = q * 4u + (!r ? 0u : pad ? 4u : r + 1u); + /* Just return the encoding length if no output is requested */ if (!size) return n; + /* We right backwards to ensure `out_buffer` and `binary` may alias each other + * (the output is at least as long as the input (actually always longer + * unless `len` is 0)) */ + + /* NUL-terminate output */ size -= 1u; out_buffer[n < size ? n : size] = '\0'; + /* Deal with situation where number of bytes is not divisible + * byte there, and set excess bits to 0; padding characters are + * appended if required (they are probably optional since padding + * is only useful to allow base-64 strings to be concatenated) */ if (r == 1u) { a = data[q * 3u + 0u]; switch (size > q * 4u ? size - q * 4u : 0u) { /* fall-through */ @@ -66,8 +79,11 @@ librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len, } } + /* Encode multiples of 3 bytes as multiples of 4 ASCII characters, */ i = q * 4u; j = q * 3u; + /* however, first we may have to truncate the output to a non-multiple + * of 4 ASCII characters the output buffer is too small, */ if (i > size) { q = size / 4u; r = size % 4u; @@ -87,6 +103,7 @@ librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len, break; } } + /* then we can efficiently write the multiple of 4 characters */ while (i) { i -= 4u; j -= 3u; @@ -120,6 +137,7 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t ascii_len char buf[256u]; size_t i, j, n; + /* Check internal test correctness */ if (padded_ascii_len & 3u) { padded_ascii_len |= 3u; padded_ascii_len += 1u; @@ -128,12 +146,15 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t ascii_len assert(padded_ascii_len / 4u == (ascii_len + 3u) / 4u); assert(padded_ascii_len >= ascii_len); + /* Check length-only request without padding */ EXPECT(librecrypt_encode(NULL, 0u, binary, binary_len, lut, '\0') == ascii_len); EXPECT(librecrypt_encode(buf, 0u, binary, binary_len, lut, '\0') == ascii_len); + /* Check length-only request with padding */ EXPECT(librecrypt_encode(NULL, 0u, binary, binary_len, lut, '=') == padded_ascii_len); EXPECT(librecrypt_encode(buf, 0u, binary, binary_len, lut, '=') == padded_ascii_len); + /* Check encoding requests, with and without truncation, without padding */ for (i = 0u; i <= ascii_len; i++) { memset(buf, 99, sizeof(buf)); EXPECT(librecrypt_encode(buf, i + 1u, binary, binary_len, lut, '\0') == ascii_len); @@ -143,6 +164,7 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t ascii_len EXPECT(buf[j] == 99); } + /* Check encoding requests, with and without truncation, with padding */ for (i = 0u; i <= padded_ascii_len; i++) { memset(buf, 99, sizeof(buf)); EXPECT(librecrypt_encode(buf, i + 1u, binary, binary_len, lut, '=') == padded_ascii_len); diff --git a/librecrypt_equal_binary.c b/librecrypt_equal_binary.c index e1d17cd..21d9224 100644 --- a/librecrypt_equal_binary.c +++ b/librecrypt_equal_binary.c @@ -30,9 +30,12 @@ librecrypt_equal_binary(const void *a, const void *b, size_t len) size_t i; unsigned char r = 0u; + /* For each character pair XOR is zero on and only on equality, + * bitwise OR of all XORs remain 0 if and only if all where equal */ for (i = 0u; i < len; i++) r = (unsigned char)(r | (*x++ ^ *y++)); + /* Prevent compiler from returning early */ (*librecrypt_explicit_____)(r); return r ? 0 : 1; diff --git a/librecrypt_fill_with_random_.c b/librecrypt_fill_with_random_.c index 475a156..8eb3cfd 100644 --- a/librecrypt_fill_with_random_.c +++ b/librecrypt_fill_with_random_.c @@ -16,7 +16,7 @@ librecrypt_fill_with_random_(void *out, size_t n, ssize_t (*rng)(void *out, size r = (*rng)(buf, n, user); if (r <= 0) { if (!r) - abort(); + abort(); /* $covered$ */ return -1; } buf = &buf[(size_t)r]; @@ -98,10 +98,12 @@ main(void) SET_UP_ALARM(); + /* Check default RNG */ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), NULL, NULL) == 0); EXPECT(librecrypt_fill_with_random_(buf2, sizeof(buf1), NULL, NULL) == 0); EXPECT(memcmp(buf1, buf2, sizeof(buf1))); + /* Check stateless all-at-once RNG */ memset(buf1, 99, sizeof(buf1)); errno = 0; EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero, NULL) == 0); @@ -109,6 +111,7 @@ main(void) for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) EXPECT(!buf1[i]); + /* Check stateful chunked RNG (importantly, chunks are smaller than pattern) */ memset(buf1, 99, sizeof(buf1)); s = 0u; errno = 0; @@ -117,6 +120,7 @@ main(void) for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) EXPECT(buf1[i] == s); + /* Check stateful one-byte-per-call RNG */ memset(buf1, 99, sizeof(buf1)); s = 0u; errno = 0; @@ -125,6 +129,7 @@ main(void) for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) EXPECT(buf1[i] == s); + /* Check failure in RNG */ memset(buf1, 99, sizeof(buf1)); s = 0u; errno = 0; @@ -133,6 +138,7 @@ main(void) for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) EXPECT(buf1[i] == 99); + /* Check function abort(3)s if RNG returns 0 */ EXPECT_ABORT(rv = librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero_ret, NULL)); return rv; diff --git a/librecrypt_find_first_algorithm_.c b/librecrypt_find_first_algorithm_.c index 406f5ac..68cc533 100644 --- a/librecrypt_find_first_algorithm_.c +++ b/librecrypt_find_first_algorithm_.c @@ -11,9 +11,15 @@ librecrypt_find_first_algorithm_(const char *settings, size_t len) size_t i; for (i = 0u;; i++) { + /* Get next algorithm in the list */ algo = &librecrypt_algorithms_[i]; if (IS_END_OF_ALGORITHMS(algo)) break; + + /* Get match-priority, bigger is more prioritised, + * 0 is non-match, so we save if we get a bigger + * value then our current best (we start at 0 + * (no match found)) */ r = (*algo->is_algorithm)(settings, len); if (r > priority) { priority = r; @@ -21,6 +27,12 @@ librecrypt_find_first_algorithm_(const char *settings, size_t len) } } + /* NULL if all `is_algorithm` returned 0 (including if the + * last was completly empty), otherwise the first one + * (actually an arbitrary one) that returned the greated + * match-priority (which doesn't necessarily mean it's better, + * which is why we don't just break at the first match + * (`librecrypt_algorithms_` is ordered by how good they are)) */ return found; } diff --git a/librecrypt_get_encoding.c b/librecrypt_get_encoding.c index dd5d33d..2eef882 100644 --- a/librecrypt_get_encoding.c +++ b/librecrypt_get_encoding.c @@ -9,18 +9,21 @@ librecrypt_get_encoding(const char *settings, size_t len, char *pad_out, int *st size_t i, start = 0u; const struct algorithm *algo; + /* Find last algorithm in the chain */ for (i = 0u; i < len; i++) if (settings[i] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) start = i + 1u; settings = &settings[start]; len -= start; + /* Identify the algorithm */ algo = librecrypt_find_first_algorithm_(settings, len); if (!algo) { errno = ENOSYS; return NULL; } + /* Return the algorithms salt/hash encoding format */ *pad_out = algo->pad; *strict_pad_out = algo->strict_pad; if (decoding) diff --git a/librecrypt_hash_.c b/librecrypt_hash_.c index ed842e9..563e072 100644 --- a/librecrypt_hash_.c +++ b/librecrypt_hash_.c @@ -14,6 +14,20 @@ zero_generator(void *out, size_t n, void *user) } +static int +has_asterisk_encoded_salt(const char *settings) +{ + const char *asterisk; + /* Check whether there is an asterisk */ + asterisk = strchr(settings, '*'); + if (!asterisk) + return 0; + /* Check that the first asterisk is not part of the hash result + * (would specify hash size) rather than be a salt */ + return !!strchr(&asterisk[1u], LIBRECRYPT_HASH_COMPOSITION_DELIMITER); +} + + ssize_t librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved, enum action action) @@ -23,25 +37,35 @@ librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, siz char *settings_scratch = NULL; char *phrase_scratches[2] = {NULL, NULL}; size_t phrase_scratch_sizes[2] = {0u, 0u}; - size_t n, ascii_len, min, prefix, ret = 0u; + size_t i, n, ascii_len, min, prefix, ret = 0u; + size_t hash_size, digit, quotient, remainder; int has_next, phrase_scratch_i = 0; ssize_t r_len; int r; void *new; - if (reserved != NULL) { - errno = EINVAL; - return -1; - } + /* Ensure the reserved parameter is NULL */ + if (reserved != NULL) + goto einval; - if (!size) - rng = &zero_generator; + /* Realise asterisk-encoded salts */ + if (has_asterisk_encoded_salt(settings)) { + /* Only `librecrypt_crypt` outputs the configrations, + * and thus only `librecrypt_crypt` outputs salts, so + * `librecrypt_hash` and `librecrypt_hash_binary` may + * not use asterisk-encoding for salts as the generated + * salt would be lost (use `librecrypt_realise_salts` + * first instead) */ + if (action != ASCII_CRYPT) + goto einval; - if (strchr(settings, '*')) { - if (action != ASCII_CRYPT) { - errno = EINVAL; - return -1; - } + /* If there no output, don't waste time and entropy + * generating random salts, just write generates + * zeroes instead */ + if (!size) + rng = &zero_generator; + + /* Generate the salts */ r_len = librecrypt_realise_salts(out_buffer, size, settings, rng, NULL); if (r_len < 0) { if (errno == ERANGE) @@ -58,6 +82,7 @@ librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, siz } next: + /* Measure algorithm configuration size until next-algorithm marker (or end of string) */ has_next = 0; for (n = 0u; settings[n]; n++) { if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { @@ -66,19 +91,77 @@ next: } } + /* Identify the algorithm */ algo = librecrypt_find_first_algorithm_(settings, n); if (!algo) { errno = ENOSYS; goto fail; } - prefix = (*algo->get_prefix)(settings, n); - if (has_next && prefix < n) { - errno = EINVAL; - goto fail; + /* Get length of algorithm configuration text sans hash size */ + prefix = 0u; + for (i = 0u; i < n; i++) + if (settings[i] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER) + prefix = i + 1u; + /* TODO "_" is a prefix that is being used */ + if (!algo->flexible_hash_size && prefix != n) + goto einval; + + /* Get hash size */ + if (!algo->flexible_hash_size) { + /* fixed */ + hash_size = algo->hash_size; + } else if (prefix == n) { + /* default */ + hash_size = algo->hash_size; + } else if (settings[prefix] == '*') { + /* hash length encoded */ + i = prefix + 1u; + hash_size = 0u; + for (; i < n; i++) { + if ('0' > settings[i] || settings[i] > '9') + goto einval; + digit = (size_t)(settings[i] - '0'); + if (hash_size > (SIZE_MAX - digit) / 10u) + goto einval; + hash_size *= 10u; + hash_size += digit; + } + if (!hash_size) + goto einval; + } else if (has_next) { + /* hash encoded, but is intermediate hash */ + goto einval; + } else { + /* hash encoded, and is final hash */ + for (i = prefix; i < n; i++) + if (algo->decoding_lut[(unsigned char)settings[i]] == 0xFFu) + break; + hash_size = i - prefix; + if (algo->pad && algo->strict_pad) { + for (; i < n; i++) + if (settings[i] != algo->pad) + break; + if (i - prefix % 4u) + goto einval; + if (i - prefix - hash_size >= 4u) + goto einval; + } + if (i != n) + goto einval; + quotient = hash_size / 4u; + remainder = hash_size % 4u; + if (remainder == 1u) + goto einval; + hash_size = quotient * 3u + (remainder ? remainder - 1u : 0u); } + /* For `librecrypt_crypt`: copy hash configurations to output */ if (action == ASCII_CRYPT) { + if (has_next) { + /* Include hash length specification */ + prefix = n; + } min = size ? size - 1u < prefix ? size - 1u : prefix : 0u; size -= min; memcpy(out_buffer, settings, min); @@ -86,9 +169,10 @@ next: ret += prefix; } - if (size && phrase_scratch_sizes[phrase_scratch_i] < algo->hash_size) { + /* Unless output is fully truncated, ensure scratch for intermediate hash is large enough */ + if (size && phrase_scratch_sizes[phrase_scratch_i] < hash_size) { librecrypt_wipe(phrase_scratches[phrase_scratch_i], phrase_scratch_sizes[phrase_scratch_i]); - new = realloc(phrase_scratches[phrase_scratch_i], algo->hash_size); + new = realloc(phrase_scratches[phrase_scratch_i], hash_size); if (!new) { free(phrase_scratches[phrase_scratch_i]); phrase_scratches[phrase_scratch_i] = NULL; @@ -96,42 +180,57 @@ next: goto fail; } phrase_scratches[phrase_scratch_i] = new; - phrase_scratch_sizes[phrase_scratch_i] = algo->hash_size; + phrase_scratch_sizes[phrase_scratch_i] = hash_size; } + /* Calculate intermediate or final hash */ if (has_next) { + /* Intermediate hash: write to scratch */ hash_to_scratch: r = (*algo->hash)(size ? phrase_scratches[phrase_scratch_i] : NULL, size ? phrase_scratch_sizes[phrase_scratch_i] : 0u, - phrase, len, settings, prefix, reserved); + phrase, len, settings, n, reserved); } else if (action == BINARY_HASH) { + /* Final hash in binary: write immediate to output */ hash_to_output: - r = (*algo->hash)(out_buffer, size, phrase, len, settings, prefix, reserved); - } else if (size < algo->hash_size) { + r = (*algo->hash)(out_buffer, size, phrase, len, settings, n, reserved); + } else if (size < hash_size) { + /* Final hash in ASCII: write to scratch if output is truncated, + * because it will be converted to ASCII later */ goto hash_to_scratch; } else { + /* Final hash in ASCII: write immedate to output if it fits, + * will be converted to ASCII later */ goto hash_to_output; } if (r < 0) goto fail; + /* Maybe convert to ASCII and get hash size */ if (!has_next) { + /* Final hash: */ if (action == BINARY_HASH) { - ret += algo->hash_size; + /* Binary output: we already have the has in binary, + * so yes add the length to the return value */ + ret += hash_size; } else if (!size) { - ascii_len = algo->hash_size % 3u; + /* ASCII hash but not output: just calculate the + * ASCII length and add it to the return value */ + ascii_len = hash_size % 3u; if (ascii_len) { if (algo->pad && algo->strict_pad) - ascii_len = 4u; + ascii_len = 4u; /* padding to for bytes */ else - ascii_len += 1u; + ascii_len += 1u; /* 3n+m bytes: 4n+m+1 chars, unless m=0 */ } - ascii_len += algo->hash_size / 3u * 4u; + ascii_len += hash_size / 3u * 4u; goto include_ascii; } else { + /* ASCII output: convert from binary to ASCII, + * and add ASCII length to the return value */ ascii_len = librecrypt_encode(out_buffer, size, - size < algo->hash_size ? phrase_scratches[phrase_scratch_i] : out_buffer, - algo->hash_size, algo->encoding_lut, algo->strict_pad ? algo->pad : '\0'); + size < hash_size ? phrase_scratches[phrase_scratch_i] : out_buffer, + hash_size, algo->encoding_lut, algo->strict_pad ? algo->pad : '\0'); include_ascii: min = size ? size - 1u < ascii_len ? size - 1u : ascii_len : 0u; out_buffer = &out_buffer[min]; @@ -139,11 +238,21 @@ next: ret += ascii_len; } } else { + /* Intermediate hash: */ + + /* Swap scratches, so that the intermediate output + * becomes the next algorithm's input, but use NULL if output is + * truncated (measure only) */ phrase = size ? phrase_scratches[phrase_scratch_i] : NULL; phrase_scratch_i ^= 1; - len = algo->hash_size; + len = hash_size; + /* Seek past the algorithm settings */ settings = &settings[n]; + /* and the '>' */ + settings++; + + /* For `librecrypt_crypt`: add '>' to the password hash string */ if (action == ASCII_CRYPT) { ret += 1u; if (size) { @@ -151,10 +260,12 @@ next: size -= 1u; } } - settings++; + + /* Calculate the hash, from the intermediate output */ goto next; } + /* Erase and deallocate scratch memory */ librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]); librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]); librecrypt_wipe_str(settings_scratch); @@ -162,10 +273,14 @@ next: free(phrase_scratches[1u]); free(settings_scratch); + /* NUL-terminate output if it is a string (`out_buffer` is offset at every write to it) */ if (size && action != BINARY_HASH) out_buffer[0] = '\0'; + return (ssize_t)ret; +einval: + errno = EINVAL; fail: librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]); librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]); diff --git a/librecrypt_make_settings.c b/librecrypt_make_settings.c index 99c7e3b..49996c1 100644 --- a/librecrypt_make_settings.c +++ b/librecrypt_make_settings.c @@ -9,23 +9,29 @@ librecrypt_make_settings(char *out_buffer, size_t size, const char *algorithm, s { const struct algorithm *algo; + /* Get algorithm */ if (!algorithm) { + /* Select best algorithm if `NULL` is specified */ algo = &librecrypt_algorithms_[0]; if (IS_END_OF_ALGORITHMS(algo)) goto enosys; } else { + /* Verify single, unchained algorithm is specified if not `NULL`*/ if (strchr(algorithm, LIBRECRYPT_ALGORITHM_LINK_DELIMITER)) { errno = EINVAL; return -1; } + /* Identify the algorithm */ algo = librecrypt_find_first_algorithm_(algorithm, strlen(algorithm)); if (!algo) goto enosys; } + /* Use default random number generator if none was specified */ if (!rng) rng = &librecrypt_rng_; + /* Configure */ return (*algo->make_settings)(out_buffer, size, algorithm, memcost, timecost, gensalt, rng, user); enosys: diff --git a/librecrypt_next_algorithm.c b/librecrypt_next_algorithm.c index b825582..8a44815 100644 --- a/librecrypt_next_algorithm.c +++ b/librecrypt_next_algorithm.c @@ -31,9 +31,10 @@ testcase_1(void) EXPECT(!strcmp(s, "e$f$")); EXPECT((a = librecrypt_next_algorithm(&s))); - EXPECT(s == NULL); + EXPECT(s == NULL); /* state is set to NULL when done */ EXPECT(!strcmp(a, "e$f$")); + /* Check librecrypt_next_algorithm when done returns NULL without changing state */ EXPECT(librecrypt_next_algorithm(&s) == NULL); EXPECT(s == NULL); } @@ -61,9 +62,10 @@ testcase_2(void) EXPECT(!strcmp(s, "")); EXPECT((a = librecrypt_next_algorithm(&s))); - EXPECT(s == NULL); + EXPECT(s == NULL); /* state is set to NULL when done */ EXPECT(!strcmp(a, "")); + /* Check librecrypt_next_algorithm when done returns NULL without changing state */ EXPECT(librecrypt_next_algorithm(&s) == NULL); EXPECT(s == NULL); } diff --git a/librecrypt_realise_salts.3 b/librecrypt_realise_salts.3 index 8a743cc..0e314e2 100644 --- a/librecrypt_realise_salts.3 +++ b/librecrypt_realise_salts.3 @@ -12,8 +12,6 @@ ssize_t \fBlibrecrypt_realise_salts\fP(char *restrict \fIout_buffer\fP, size_t \ .PP Link with .IR -lrecrypt . -Static linking may require additional flags -depending on enabled hash algorithms. .SH DESCRIPTION The @@ -24,7 +22,9 @@ non-negative decimal number in and replaces each with ASCII-encoded random bytes (as many bytes as the number specifies), writing the result to -.IR out_buffer . +.IR out_buffer , +except those that are used to specify the +desired hash size. .PP .IR *rng , or a default implementation (which tries to use @@ -106,9 +106,6 @@ function will fail if: .B ERANGE The expected return value is greater than {SSIZE_MAX}. .TP -.B EINVAL -Asterisk-encoding was used in an invalid context. -.TP .B ENOSYS .I settings contain an algorithm that is not recognised or was diff --git a/librecrypt_realise_salts.c b/librecrypt_realise_salts.c index a4204a6..dada7f1 100644 --- a/librecrypt_realise_salts.c +++ b/librecrypt_realise_salts.c @@ -9,71 +9,58 @@ librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *set { const char *lut; char pad; - int strict_pad, has_asterisk, nul_term = 0; - size_t i, j, n, min, ret = 0u; + int strict_pad, nul_term = 0; + size_t i, min, nasterisks, prefix, ret = 0u; size_t count, digit, q, r, left, mid, right; + /* If we are doing output, it should be NUL-terminated */ if (size) { nul_term = 1; size -= 1u; } - for (;;) { - for (; *settings == LIBRECRYPT_ALGORITHM_LINK_DELIMITER; settings++) { - if (size) { - *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER; - size -= 1u; - } - ret += 1u; - } - - if (!*settings) - break; - - has_asterisk = 0; - for (n = 0u; settings[n] && settings[n] != LIBRECRYPT_ALGORITHM_LINK_DELIMITER; n++) - if (settings[n] == '*') - has_asterisk = 1; - - if (!has_asterisk) { - min = size < n ? size : n; - memcpy(out_buffer, settings, min); - out_buffer += min; - settings = &settings[n]; - ret += n; - continue; - } - - if (*settings == '*') { - errno = EINVAL; - return -1; + /* For each chained algorithm */ + while (*settings) { + /* Get the number of '*' that are not in the result + * (hash size specification), and also length of + * the algorithm configuration, so we can identify + * the algorithm next and get its binary data encoding + * format (just calling librecrypt_get_encoding on the + * entire string would get it for the last algorithm + * in the chain) */ + nasterisks = 0u; + count = 0u; + for (prefix = 0u; settings[prefix]; prefix++) { + if (settings[prefix] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER) + nasterisks = count; + else if (settings[prefix] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + break; + else if (settings[prefix] == '*') + count += 1u; } - lut = librecrypt_get_encoding(settings, n, &pad, &strict_pad, 0); + /* Get binary data encoding format */ + lut = librecrypt_get_encoding(settings, prefix, &pad, &strict_pad, 0); if (!lut) return -1; pad = strict_pad ? pad : '\0'; - for (i = 0; i < n;) { - if (settings[i] != '*') { - if (size) { - *out_buffer++ = settings[i]; - size -= 1u; - } - ret += 1u; - i++; - continue; - } + /* Process each asterisk-encoded salt */ + while (nasterisks--) { + /* Copy text before next '*' */ + for (i = 0u; settings[i] != '*'; i++); + min = i < size ? i : size; + memcpy(out_buffer, settings, min); + size -= min; + settings = &settings[i]; + ret += i; - if (++i == n) { - if (size) { - *out_buffer++ = '*'; - size -= 1u; - } - ret += 1u; - break; - } - if ('0' > settings[i] || settings[i] > '9') { + /* Skip past the '*' */ + settings++; + + /* If the '*' is not followed by an unsigned, + * decimal integer include it literally */ + if ('0' > settings[1u] || settings[1u] > '9') { if (size) { *out_buffer++ = '*'; size -= 1u; @@ -82,49 +69,84 @@ librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *set continue; } - count = 0; - for (; i < n && '0' <= settings[i] && settings[i] <= '9'; i++) { - digit = (size_t)(settings[i] - '0'); + /* Parse encoded integer */ + count = 0u; + while ('0' <= *settings && *settings <= '9') { + digit = (size_t)(*settings++ - '0'); if (count > ((size_t)SSIZE_MAX - digit) / 10u) goto erange; count *= 10u; count += digit; } + /* Get number of random base-64 characters to generated, + * and the number of padding characters to append */ + /* 1 byte (8 bits) requires 2 base-64 characters (12 bits, 4 bits extra), + * 2 bytes (16 bits) requires 3 base-64 characters (18 bits, 2 bits extra), and + * 3 bytes (25 bits) requires 4 base-64 characters (24 bits) exactly */ q = count / 3u; r = count % 3u; - mid = r ? r + 1u : 0u; - right = (!r ? 0u : pad ? 4u : r + 1u); + /* Full alphabet */ + left = q * 4u + r; + /* Partial alphabet (extra bits are encoded as 0) */ + mid = r ? 1u : 0u; + /* Padding: r==0: no padding, + * r==1: 2 base-64 characters, so 2 padding characters, + * r==2: 3 base-64 characters, so 1 padding characters */ + right = (r && pad) ? 3u - r : 0u; + /* Get total length */ if (q > ((size_t)SSIZE_MAX - right) / 4u) goto erange; - left = q * 4u; ret += left + mid + right; + /* Make sure we don't write more random characters than + * we have room for; we add `mid` into `left` so we have + * one variable for all random characters */ left += mid; if (left > size) { - left = size; - mid = 0; + left = 0u; + mid = 0u; } + + /* Write random characters */ if (librecrypt_fill_with_random_(out_buffer, left, rng, user)) return -1; - for (j = 0; j < left; j++) - out_buffer[j] = lut[((unsigned char *)out_buffer)[j]]; - if (mid) - out_buffer[j] = lut[((unsigned char *)out_buffer)[j] & (r == 1u ? ~15u : ~3u)]; + for (i = 0u; i < left; i++) + out_buffer[i] = lut[((unsigned char *)out_buffer)[i]]; + if (mid) { + i = left - 1u; + out_buffer[i] = lut[((unsigned char *)out_buffer)[i] & (r == 1u ? ~15u : ~3u)]; + } out_buffer = &out_buffer[left]; size -= left; - if (right > size) - right = size; - for (j = 0; j < right; j++) + /* Write padding charaters */ + right = right < size ? right : size; + for (i = 0u; i < right; i++) out_buffer[right] = pad; out_buffer = &out_buffer[right]; size -= right; } + + /* Copy remainder of the algorithm configuration, and the '>' if intermediate */ + for (i = 0u; settings[i];) + if (settings[i++] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + break; + min = i < size ? i : size; + memcpy(out_buffer, settings, min); + size -= min; + settings = &settings[i]; + ret += i; } + /* NUL-terminate the output if we were doing output */ if (nul_term) *out_buffer = '\0'; + + /* Return the number of written bytes (excluding NUL byte): + * the length of the new password hash string, but ensure + * the new salts where not so large that our return value + * is out of range */ if (ret > (size_t)SSIZE_MAX) goto erange; return (ssize_t)ret; diff --git a/librecrypt_rng_.c b/librecrypt_rng_.c index 8b9f138..d7fbee3 100644 --- a/librecrypt_rng_.c +++ b/librecrypt_rng_.c @@ -6,7 +6,7 @@ ssize_t librecrypt_rng_(void *out, size_t n, void *user) { - static unsigned int seed = 0u; + static volatile unsigned int seed = 0u; unsigned char *buf = out; int v, fd, saved_errno = errno; unsigned int state; @@ -15,17 +15,21 @@ librecrypt_rng_(void *out, size_t n, void *user) struct timespec ts; #if defined(__linux__) && defined(AT_RANDOM) const unsigned char *at_random; - uintptr_t at_random_addr; #endif + void *random_ptr; + uintptr_t random_addr; (void) user; + /* Done if nothing was requested */ if (!n) return 0; + /* Make sure we don't return more than in range for the return value */ if (n > (size_t)SSIZE_MAX) n = (size_t)SSIZE_MAX; + /* Use Linux's system call version of /dev/urandom */ #if defined(__linux__) for (;;) { r = getrandom(buf, n, 0u); @@ -45,6 +49,7 @@ librecrypt_rng_(void *out, size_t n, void *user) } #endif + /* Use getentropy(3posix) */ for (;;) { min = n < 256u ? n : 256u; if (getentropy(buf, min)) { @@ -60,6 +65,7 @@ librecrypt_rng_(void *out, size_t n, void *user) } } + /* Use /dev/urandom if available */ #ifndef O_CLOEXEC # define O_CLOEXEC 0 #endif @@ -88,29 +94,61 @@ librecrypt_rng_(void *out, size_t n, void *user) } } + /* Last restort, use non-entropy based RNG */ + /* but try to use a good seed */ state = seed; if (!state) { + /* Hopefully the application has already called srand(3) */ state = (unsigned int)rand(); - state ^= (unsigned int)time(NULL); - if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { - state ^= (unsigned int)ts.tv_sec; - state ^= (unsigned int)ts.tv_nsec; - } #if defined(__linux__) && defined(AT_RANDOM) - at_random_addr = (uintptr_t)getauxval(AT_RANDOM); - if (at_random_addr) { - at_random = (const void *)at_random_addr; + /* Otherwise can we use random data the kernel gave to the process */ + random_addr = (uintptr_t)getauxval(AT_RANDOM); + if (random_addr) { + at_random = (const void *)random_addr; for (i = 0u; i < 16u; i++) state ^= (unsigned int)at_random[i] << (i % sizeof(unsigned int) * 8u); + goto have_initial_seed; } - } #endif +#ifndef MAP_UNINITIALIZED +# define MAP_UNINITIALIZED 0 +#endif + /* But where that is not supported, we can hopefully + * get a random address: here mmap(2) is much better than + * malloc(3), as malloc(3) may be less random when the + * allocation is small, and we don't want to make a big + * allocation. A few bit's are always 0, but that's not + * a big deal. */ + random_ptr = mmap(NULL, 1u, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, -1, 0); + if (random_ptr != MAP_FAILED) { + random_addr = (uintptr_t)random_ptr; + state ^= (unsigned int)random_addr; + munmap(random_ptr, 1u); + goto have_initial_seed; /* just using goto to simplify the #if'ing */ + } + random_ptr = malloc(1u); /* NULL is OK (MAP_FAILED was actually also OK) */ + random_addr = (uintptr_t)random_ptr; + state ^= (unsigned int)random_addr; + free(random_ptr); + } +have_initial_seed: + /* and always do a time-based reseed in case of multithreading, + * so multiple passwords don't end up using the same salt */ + if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { + state ^= (unsigned int)ts.tv_sec; + state ^= (unsigned int)ts.tv_nsec; + } else { + state ^= (unsigned int)time(NULL); + } + /* rand(3) is good enough on modern systems: all bits are equality random */ for (i = 0u; i < n; i++) { v = rand_r(&state); v = (int)((uintmax_t)v * 255u / (uintmax_t)RAND_MAX); buf[i] = (unsigned char)v; } ret += n; + /* Of course we have to save the RNG state so next run + * depends both of the this run and the time */ seed = state; out: @@ -130,15 +168,20 @@ main(void) unsigned char buf1[1024u]; unsigned char buf2[sizeof(buf1)]; ssize_t n1, n2; + void *user = NULL; SET_UP_ALARM(); + /* Check that output is random */ n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); - n2 = librecrypt_rng_(buf2, sizeof(buf2), NULL); + n2 = librecrypt_rng_(buf2, sizeof(buf2), &user); EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1)); EXPECT(n2 >= 128 && (size_t)n2 <= sizeof(buf2)); EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2))); + /* Check zero-request */ + EXPECT(librecrypt_rng_(NULL, 0u, NULL) == 0u); + return 0; } diff --git a/librecrypt_settings_prefix.3 b/librecrypt_settings_prefix.3 index 622af0e..6420c3e 100644 --- a/librecrypt_settings_prefix.3 +++ b/librecrypt_settings_prefix.3 @@ -6,7 +6,7 @@ librecrypt_settings_prefix - Get length of settings prefix in a password hash st .nf #include -size_t \fBlibrecrypt_settings_prefix\fP(const char *\fIhash\fP); +size_t \fBlibrecrypt_settings_prefix\fP(const char *\fIhash\fP, size_t *\fIhashsize_out\fP); .fi .PP Link with @@ -19,16 +19,30 @@ function returns the number of bytes, from the front of .IR hash , that make up the algorithm configuration. .PP -The string is scanned for the delimiters -.I LIBRECRYPT_HASH_COMPOSITION_DELIMITER -(which is -.BR \(aq$\(aq ) -and -.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER -(which is -.BR \(aq>\(aq ), -and the returned value is one byte past the -last occurrence, or 0 if none was found. +Unless +.I hashsize_out +is +.IR NULL , +the value 0 +is stored in +.I *hashsize_out +if the output hash size is fixed for the last algorithm in +.I hash +or if the output hash size is not specified (either because +.IR &hash[r] , +where +.I r +is the return value, is the null byte, and so the default +hash size shall be used or because the information is +malformatted), otherwise a positive value is stored in +.IR *hashsize_out , +specifying the number of bytes in the binary output hash, +as determined by the text in +.I hash +beyond the settings prefix. +See the +.B EXTENDED DESCRIPTION +section for more information. .PP .I hash must not be @@ -69,6 +83,22 @@ T} Async-signal safety AS-Safe .TE .sp +.SH EXTENDED DESCRIPTION +Some algorithms have flexible hash size which +is encoded either with an actual hash (its +length after decoding to binary is checked) +or using asterisk-notation in place of the +result (that is, as an asterisk +.RI (\(aq * \(aq) +followed by an unsigned, decimal integer, +which may have leading zeroes). This part +is always excluded (from the last algorithm +in the algorithm chain in +.IR hash ) +in the return value of the +.BR librecrypt_settings_prefix () +function. + .SH HISTORY The .BR librecrypt_settings_prefix () diff --git a/librecrypt_settings_prefix.c b/librecrypt_settings_prefix.c index edc7222..7568bb3 100644 --- a/librecrypt_settings_prefix.c +++ b/librecrypt_settings_prefix.c @@ -3,30 +3,112 @@ #ifndef TEST -extern inline size_t librecrypt_settings_prefix(const char *hash); +size_t +librecrypt_settings_prefix(const char *hash, size_t *hashsize_out) +{ + size_t i, len, ret = 0u; + size_t last_offset = 0u; + const struct algorithm *algo; + uintmax_t hashsize; + + /* Find last algorithm, and beginning of result */ + for (i = 0u; hash[i]; i++) { + if (hash[i] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER) + ret = i + 1u; + else if (hash[i] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + last_offset = ret = i + 1u; + } + /* and get `strlen(hash)` */ + len = i; + + /* TODO "_" is a prefix that is being used */ + + /* Return if hash size is not required */ + if (!hashsize_out) + goto out; + /* Return 0 as hash size if default is specified */ + if (ret == i) + goto zero; + /* Return 0 as hash size if algorithm cannot be identified or has fixed hash size */ + algo = librecrypt_find_first_algorithm_(&hash[last_offset], len - last_offset); + if (!algo || !algo->flexible_hash_size) + goto zero; + + /* Get the hash size */ + if (!librecrypt_check_settings_(&hash[ret], len - ret, "%^b", + &hashsize, (uintmax_t)1u, (uintmax_t)SIZE_MAX, + algo->decoding_lut, algo->pad, algo->strict_pad)) + goto zero; + + *hashsize_out = (size_t)hashsize; + goto out; + +zero: + *hashsize_out = 0u; +out: + return ret; +} #else -#define CHECK(PREFIX, SUFFIX)\ +#define CHECK_NULL(PREFIX, SUFFIX)\ + do {\ + EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX, NULL) == sizeof(PREFIX) - 1u);\ + EXPECT(librecrypt_settings_prefix(PREFIX, NULL) == sizeof(PREFIX) - 1u); \ + } while (0) + +#if 0 +#define CHECK_ASTERISK(PREFIX, SUFFIX, HASH)\ + do {\ + size_t hashsize = 99999u;\ + EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX #HASH, &hashsize) == sizeof(PREFIX) - 1u);\ + EXPECT(hashsize == HASH##u);\ + } while (0) + +#define CHECK_ZERO(PREFIX, SUFFIX)\ do {\ - EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX) == sizeof(PREFIX) - 1u);\ - EXPECT(librecrypt_settings_prefix(PREFIX) == sizeof(PREFIX) - 1u);\ + size_t hashsize = 99999u;\ + EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX, &hashsize) == sizeof(PREFIX) - 1u);\ + EXPECT(hashsize == 0u);\ } while (0) +#define CHECK_HASHED(PREFIX, SUFFIX, HASH)\ + do {\ + size_t hashsize = 99999u;\ + EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX, &hashsize) == sizeof(PREFIX) - 1u);\ + EXPECT(hashsize == HASH##u);\ + } while (0) +#endif + int main(void) { SET_UP_ALARM(); - CHECK("", "result"); - CHECK(">", "double-des result"); - CHECK("$", "x"); - CHECK("y$", "x"); - CHECK("y>", "x"); - CHECK("a$b$c>d$e$", "x"); - CHECK("a$b$c>", "x"); + + /* Simple cases */ + CHECK_NULL("", "result"); + CHECK_NULL(">", "double-des result"); + CHECK_NULL("$", "x"); + CHECK_NULL("y$", "x"); + CHECK_NULL("y>", "x"); + CHECK_NULL("a$b$c>d$e$", "x"); + CHECK_NULL("a$b$c>", "x"); + + /* Discard asterisk-notation */ + CHECK_NULL("", "*10"); + CHECK_NULL(">", "*11"); + CHECK_NULL("$", "*12"); + CHECK_NULL("y$", "*13"); + CHECK_NULL("y>", "*012"); + CHECK_NULL("a$b$c>d$e$", "*1"); + CHECK_NULL("a$b$c>", "*2"); + CHECK_NULL("a$b$c>*10$", "*2"); + + /* TODO test hash size output (requires algorithms) */ + return 0; } diff --git a/librecrypt_test_supported.c b/librecrypt_test_supported.c index fad8bd9..71daad1 100644 --- a/librecrypt_test_supported.c +++ b/librecrypt_test_supported.c @@ -7,38 +7,35 @@ int librecrypt_test_supported(const char *phrase, size_t len, int text, const char *settings) { const struct algorithm *algo; - size_t n, prefix; - int has_next; + size_t n; + /* For each chained algorithm*/ for (;;) { - has_next = 0; - for (n = 0u; settings[n]; n++) { - if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { - has_next = 1; + /* Measure until next '>' */ + for (n = 0u; settings[n]; n++) + if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) break; - } - } + /* Identify algorithm */ algo = librecrypt_find_first_algorithm_(settings, n); if (!algo) return 0; - prefix = (*algo->get_prefix)(settings, n); - if (has_next && prefix < n) + /* Check configuration and input support, and get hash size */ + if (!(*algo->test_supported)(phrase, len, text, settings, n, &len)) return 0; - if (!(*algo->test_supported)(phrase, len, text, settings, prefix)) - return 0; - - if (!has_next) + /* Return just process last chained algorithm */ + if (!settings[n]) return 1; + /* Hashes are binary */ phrase = NULL; - len = algo->hash_size; text = 0; - settings = &settings[n]; - settings++; + /* Goto next algorithm */ + settings = &settings[n]; /* conf */ + settings++; /* '>' */ } } diff --git a/librecrypt_wipe_str.c b/librecrypt_wipe_str.c index 8c31cdc..81f9d54 100644 --- a/librecrypt_wipe_str.c +++ b/librecrypt_wipe_str.c @@ -25,13 +25,19 @@ main(void) { char buf[64u]; size_t i; + SET_UP_ALARM(); + + /* Check NULL is supported */ librecrypt_wipe_str(NULL); + + /* Check normal cases */ CHECK(""); CHECK("hello"); CHECK("hello developer"); CHECK(" hello developer "); CHECK("\1 hello developer \1"); + return 0; } -- cgit v1.2.3-70-g09d2