aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DEPENDENCIES33
-rw-r--r--Makefile32
-rw-r--r--algorithms.h39
-rw-r--r--argon2/argon2.h91
-rw-r--r--argon2/hash.c36
-rw-r--r--argon2/is_algorithm.c81
-rw-r--r--argon2/make_settings.c66
-rw-r--r--argon2/prefix.mk6
-rw-r--r--argon2/suffix.mk52
-rw-r--r--argon2/test_supported.c59
-rw-r--r--common.h114
-rw-r--r--config-coverage-gcc.mk11
-rw-r--r--librecrypt.h101
-rw-r--r--librecrypt_add_algorithm.323
-rw-r--r--librecrypt_add_algorithm.c101
-rw-r--r--librecrypt_algorithms_.c67
-rw-r--r--librecrypt_chain_length.c3
-rw-r--r--librecrypt_check_settings_.c255
-rw-r--r--librecrypt_common_rfc4848s4_decoding_lut_.c58
-rw-r--r--librecrypt_common_rfc4848s4_encoding_lut_.c53
-rw-r--r--librecrypt_decode.c110
-rw-r--r--librecrypt_decompose_chain.c3
-rw-r--r--librecrypt_decompose_chain1.c5
-rw-r--r--librecrypt_encode.c22
-rw-r--r--librecrypt_equal_binary.c3
-rw-r--r--librecrypt_fill_with_random_.c8
-rw-r--r--librecrypt_find_first_algorithm_.c12
-rw-r--r--librecrypt_get_encoding.c3
-rw-r--r--librecrypt_hash_.c177
-rw-r--r--librecrypt_make_settings.c6
-rw-r--r--librecrypt_next_algorithm.c6
-rw-r--r--librecrypt_realise_salts.39
-rw-r--r--librecrypt_realise_salts.c154
-rw-r--r--librecrypt_rng_.c67
-rw-r--r--librecrypt_settings_prefix.352
-rw-r--r--librecrypt_settings_prefix.c104
-rw-r--r--librecrypt_test_supported.c31
-rw-r--r--librecrypt_wipe_str.c6
38 files changed, 1778 insertions, 281 deletions
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 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];
-#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];
+/**
+ * RFC 4648 §4 implementation of `struct algorithm.decoding_lut_`
+ */
+extern const unsigned char librecrypt_common_rfc4848s4_decoding_lut_[256u];
-#define X(ALGO) IF__##ALGO##__SUPPORTED(Y(ALGO))
-LIST_ALGORITHMS(X)
-#undef X
-#undef Y
+#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 <libar2.h>
+#include <libar2simplified.h>
+
+
+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 <libar2.h>
+
+
+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 <sys/auxv.h>
# include <sys/random.h>
#endif
+#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
-#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
*/
@@ -77,20 +94,6 @@ enum action {
*/
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`
@@ -191,10 +196,16 @@ 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 <sys/wait.h>
# include <assert.h>
# include <signal.h>
-# include <stdio.h>
# include <string.h>
# include <unistd.h>
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 <librecrypt.h>
-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;
}