From d77ab463184d113ca6119403887c9f4ed0dfdf0b Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sun, 26 Apr 2026 22:36:47 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 16 + LICENSE | 15 + Makefile | 126 ++++++ algorithms.h | 25 ++ common.h | 426 ++++++++++++++++++++ config.mk | 8 + librecrypt.h | 776 +++++++++++++++++++++++++++++++++++++ librecrypt_add_algorithm.3 | 134 +++++++ librecrypt_add_algorithm.c | 96 +++++ librecrypt_algorithms_.c | 69 ++++ librecrypt_chain_length.3 | 69 ++++ librecrypt_chain_length.c | 27 ++ librecrypt_crypt.3 | 131 +++++++ librecrypt_crypt.c | 26 ++ librecrypt_decode.3 | 104 +++++ librecrypt_decode.c | 257 ++++++++++++ librecrypt_decompose_chain.3 | 98 +++++ librecrypt_decompose_chain.c | 153 ++++++++ librecrypt_decompose_chain1.3 | 75 ++++ librecrypt_decompose_chain1.c | 37 ++ librecrypt_encode.3 | 93 +++++ librecrypt_encode.c | 177 +++++++++ librecrypt_equal.3 | 70 ++++ librecrypt_equal.c | 39 ++ librecrypt_equal_binary.3 | 65 ++++ librecrypt_equal_binary.c | 77 ++++ librecrypt_fill_with_random_.c | 142 +++++++ librecrypt_find_first_algorithm_.c | 41 ++ librecrypt_get_encoding.3 | 112 ++++++ librecrypt_get_encoding.c | 46 +++ librecrypt_hash.3 | 135 +++++++ librecrypt_hash.c | 26 ++ librecrypt_hash_.c | 187 +++++++++ librecrypt_hash_binary.3 | 138 +++++++ librecrypt_hash_binary.c | 26 ++ librecrypt_make_settings.3 | 177 +++++++++ librecrypt_make_settings.c | 50 +++ librecrypt_next_algorithm.3 | 96 +++++ librecrypt_next_algorithm.c | 82 ++++ librecrypt_realise_salts.3 | 161 ++++++++ librecrypt_realise_salts.c | 151 ++++++++ librecrypt_rng_.c | 146 +++++++ librecrypt_settings_prefix.3 | 80 ++++ librecrypt_settings_prefix.c | 34 ++ librecrypt_test_supported.3 | 98 +++++ librecrypt_test_supported.c | 59 +++ librecrypt_wipe.3 | 57 +++ librecrypt_wipe.c | 65 ++++ librecrypt_wipe_str.3 | 61 +++ librecrypt_wipe_str.c | 39 ++ mk/linux.mk | 6 + mk/macos.mk | 6 + mk/windows.mk | 6 + 53 files changed, 5416 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 algorithms.h create mode 100644 common.h create mode 100644 config.mk create mode 100644 librecrypt.h create mode 100644 librecrypt_add_algorithm.3 create mode 100644 librecrypt_add_algorithm.c create mode 100644 librecrypt_algorithms_.c create mode 100644 librecrypt_chain_length.3 create mode 100644 librecrypt_chain_length.c create mode 100644 librecrypt_crypt.3 create mode 100644 librecrypt_crypt.c create mode 100644 librecrypt_decode.3 create mode 100644 librecrypt_decode.c create mode 100644 librecrypt_decompose_chain.3 create mode 100644 librecrypt_decompose_chain.c create mode 100644 librecrypt_decompose_chain1.3 create mode 100644 librecrypt_decompose_chain1.c create mode 100644 librecrypt_encode.3 create mode 100644 librecrypt_encode.c create mode 100644 librecrypt_equal.3 create mode 100644 librecrypt_equal.c create mode 100644 librecrypt_equal_binary.3 create mode 100644 librecrypt_equal_binary.c create mode 100644 librecrypt_fill_with_random_.c create mode 100644 librecrypt_find_first_algorithm_.c create mode 100644 librecrypt_get_encoding.3 create mode 100644 librecrypt_get_encoding.c create mode 100644 librecrypt_hash.3 create mode 100644 librecrypt_hash.c create mode 100644 librecrypt_hash_.c create mode 100644 librecrypt_hash_binary.3 create mode 100644 librecrypt_hash_binary.c create mode 100644 librecrypt_make_settings.3 create mode 100644 librecrypt_make_settings.c create mode 100644 librecrypt_next_algorithm.3 create mode 100644 librecrypt_next_algorithm.c create mode 100644 librecrypt_realise_salts.3 create mode 100644 librecrypt_realise_salts.c create mode 100644 librecrypt_rng_.c create mode 100644 librecrypt_settings_prefix.3 create mode 100644 librecrypt_settings_prefix.c create mode 100644 librecrypt_test_supported.3 create mode 100644 librecrypt_test_supported.c create mode 100644 librecrypt_wipe.3 create mode 100644 librecrypt_wipe.c create mode 100644 librecrypt_wipe_str.3 create mode 100644 librecrypt_wipe_str.c create mode 100644 mk/linux.mk create mode 100644 mk/macos.mk create mode 100644 mk/windows.mk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb11397 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*\#* +*~ +*.o +*.a +*.t +*.lo +*.to +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1634eae --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2026 Mattias Andrée + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..549b8fb --- /dev/null +++ b/Makefile @@ -0,0 +1,126 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OS = linux +# Linux: linux +# Mac OS: macos +# Windows: windows +include mk/$(OS).mk + + +LIB_MAJOR = 1 +LIB_MINOR = 0 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR) +LIB_NAME = recrypt + + +OBJ_PUBLIC =\ + librecrypt_settings_prefix.o\ + librecrypt_chain_length.o\ + librecrypt_decompose_chain.o\ + librecrypt_decompose_chain1.o\ + librecrypt_next_algorithm.o\ + librecrypt_encode.o\ + librecrypt_decode.o\ + librecrypt_get_encoding.o\ + librecrypt_wipe.o\ + librecrypt_wipe_str.o\ + librecrypt_equal_binary.o\ + librecrypt_equal.o\ + librecrypt_realise_salts.o\ + librecrypt_make_settings.o\ + librecrypt_hash_binary.o\ + librecrypt_hash.o\ + librecrypt_crypt.o\ + librecrypt_add_algorithm.o\ + librecrypt_test_supported.o + +OBJ_PRIVATE =\ + librecrypt_hash_.o\ + librecrypt_rng_.o\ + librecrypt_fill_with_random_.o\ + librecrypt_find_first_algorithm_.o + +OBJ = $(OBJ_PUBLIC) $(OBJ_PRIVATE) + +HDR =\ + librecrypt.h\ + common.h\ + algorithms.h + +LOBJ = $(OBJ:.o=.lo) +TOBJ = $(OBJ:.o=.to) +TEST = $(OBJ:.o=.t) +MAN3 = $(OBJ_PUBLIC:.o=.3) +MAN7 = librecrypt.7 + + +all: librecrypt.a librecrypt.$(LIBEXT) $(TEST) +$(OBJ): $(HDR) +$(LOBJ): $(HDR) +$(TOBJ): $(HDR) +$(TEST): librecrypt.a + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +.c.lo: + $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +.c.to: + $(CC) -DTEST -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +.to.t: + $(CC) -o $@ $< librecrypt.a $(LDFLAGS) + +.c.t: + $(CC) -DTEST -o $@ $< librecrypt.a $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) + +librecrypt.a: $(OBJ) + @rm -f -- $@ + $(AR) rc $@ $(OBJ) + $(AR) ts $@ > /dev/null + +librecrypt.$(LIBEXT): $(LOBJ) + $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS) + +check: $(TEST) + @set -ex;\ + for t in $(TEST); do\ + $(CHECK_PREFIX) ./$$t;\ + done + +install: librecrypt.a librecrypt.$(LIBEXT) + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man3" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man7" + cp -- librecrypt.a "$(DESTDIR)$(PREFIX)/lib/" + cp -- librecrypt.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMINOREXT)" + $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMINOREXT)" + ln -sf -- librecrypt.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMAJOREXT)" + ln -sf -- librecrypt.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBEXT)" + cp -- librecrypt.h "$(DESTDIR)$(PREFIX)/include/" + cp -P -- $(MAN3) "$(DESTDIR)$(MANPREFIX)/man3/" + cp -P -- $(MAN7) "$(DESTDIR)$(MANPREFIX)/man7/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMAJOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMINOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBEXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/librecrypt.h" + -cd -- "$(DESTDIR)$(MANPREFIX)/man3/" && rm -f -- $(MAN3) + -cd -- "$(DESTDIR)$(MANPREFIX)/man7/" && rm -f -- $(MAN7) + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib + -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) + -rm -f -- *.to *.t + +.SUFFIXES: +.SUFFIXES: .lo .o .c .to .t + +.PHONY: all check install uninstall clean diff --git a/algorithms.h b/algorithms.h new file mode 100644 index 0000000..d38dcf5 --- /dev/null +++ b/algorithms.h @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +/* included from "common.h" */ + + +/* ordered by preference */ +#define LIST_ALGORITHMS(X) /* TODO add algorithms */ + + +#define Y(ALGO)\ + HIDDEN size_t librecrypt__##ALGO##__get_prefix(const char *settings, size_t len);\ + HIDDEN unsigned librecrypt__##ALGO##__is_algorithm(const char *settings, size_t len);\ + HIDDEN int librecrypt__##ALGO##__hash(char *restrict out_buffer, size_t size, const char *phrase,\ + size_t len, const char *settings, size_t prefix, void *reserved);\ + HIDDEN int librecrypt__##ALGO##__test_supported(const char *phrase, size_t len, int text,\ + const char *settings, size_t prefix);\ + HIDDEN ssize_t librecrypt__##ALGO##__make_settings(char *out_buffer, size_t size, const char *algorithm,\ + size_t memcost, uintmax_t timecost, int gensalt,\ + ssize_t (*rng)(void *out, size_t n, void *user), void *user);\ + NONSTRING extern const char librecrypt__##ALGO##__encoding_lut[256];\ + extern const unsigned char librecrypt__##ALGO##__decoding_lut[256]; + +#define X(ALGO) IF__##ALGO##__SUPPORTED(Y(ALGO)) +LIST_ALGORITHMS(X) +#undef X +#undef Y diff --git a/common.h b/common.h new file mode 100644 index 0000000..389244f --- /dev/null +++ b/common.h @@ -0,0 +1,426 @@ +/* See LICENSE file for copyright and license details. */ +#include "librecrypt.h" +#if defined(__linux__) +# include +# include +#endif +#include +#include +#include +#include +#include +#include + +#include "algorithms.h" + + +#if defined(__GNUC__) +# define HIDDEN __attribute__((__visibility__("hidden"))) +# define CONST __attribute__((__const__)) +#else +# define HIDDEN +# define CONST +#endif + +#define NONSTRING +#if defined(__GNUC__) && !defined(__clang__) +# if __GNUC__ >= 8 +# undef NONSTRING +# define NONSTRING __attribute__((__nonstring__)) +# endif +#endif + + +/** + * Used for literal commas in macro calls + */ +#define COMMA , + +/** + * Get the number of elements in an array + * + * @param ARRAY:nondecayed-array The array + * @return :size_t The number of elements in `ARRAY` + */ +#define ELEMSOF(ARRAY) (sizeof(ARRAY) / sizeof(*(ARRAY))) + + +/** + * Which function `librecrypt_hash_` shall behave as + */ +enum action { + /** + * Used for `librecrypt_hash_binary` + * + * Output is binary and hash result-only + */ + BINARY_HASH, + + /** + * Used for `librecrypt_hash` + * + * Output is ASCII and hash result-only + */ + ASCII_HASH, + + /** + * Used for `librecrypt_hash` + * + * Output is ASCII and contains algorithm configurations + */ + ASCII_CRYPT +}; + + +/** + * Hash algorithm information and implementation + */ +struct algorithm { + /** + * Get the number of bytes that constitute + * the algorithm specification and configuration + * + * @param settings The password hash string, containing a single algorithm + * @param len The number of bytes in `settings` + * @return `len` if the string does not contain any hash result, + * otherwise the offset of `settings` where the hash + * result begins + * + * This function shall be MT-Safe and AS-Safe + */ + size_t (*get_prefix)(const char *settings, size_t len); + + /** + * Determine if a password hash string + * selects the algorithm + * + * @param settings The password hash string, containing a single algorithm + * @param len The number of bytes in `settings` + * @return A positive value if the string matches the algorithm, + * 0 otherwise + * + * If non-zero is returned for multiple algorithm, + * the first with the highest value wins + * + * This function shall be MT-Safe and AS-Safe + */ + unsigned (*is_algorithm)(const char *settings, size_t len); + + /** + * Implements `librecrypt_hash_binary` for a single hash algorithm; + * see `librecrypt_hash_binary` for more information + * + * @param out_buffer See `librecrypt_hash_binary` + * @param size See `librecrypt_hash_binary` + * @param phrase See `librecrypt_hash_binary`; may be `NULL` + * even if `len` is positive, this happens when + * `size` is too small and the hash result will + * not be included, so there is no need to actually + * calculate the hash, however `len` and `settings` + * should still be checked + * @param len See `librecrypt_hash_binary` + * @param settings See `librecrypt_hash_binary`, + * will not contains asterisk-encoding + * @param prefix The length of `settings`, in bytes + * @param reserved See `librecrypt_hash_binary` + * @return 0 on success, -1 on failure + * @throws See `librecrypt_hash_binary` + * + * This function shall be MT-Safe but may be AS-Unsafe + */ + int (*hash)(char *restrict out_buffer, size_t size, const char *phrase, size_t len, + const char *settings, size_t prefix, void *reserved); + + /** + * Check whether the hash algorithm is supported for given + * configuration and input + * + * @param phrase The password to hash, may contain NUL bytes; + * may be `NULL` even if `len` is non-zero + * @param len The number of bytes in `phrase`, if `phrase` is `NULL`, + * the function will check that the specified number of + * bytes is supported as well as any byte sequence unless + * `text` is non-zero + * @param text Assume the password is valid UTF-8 text (without NUL bytes) + * 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 + * @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); + + /** + * See `librecrypt_make_settings` + * + * @param out_buffer See `librecrypt_make_settings` + * @param size See `librecrypt_make_settings` + * @param algorithm See `librecrypt_make_settings`, + * will match the algorithm or be `NULL` + * @param memcost See `librecrypt_make_settings` + * @param timecost See `librecrypt_make_settings` + * @param gensalt See `librecrypt_make_settings` + * @param rng See `librecrypt_make_settings` + * @param user See `librecrypt_make_settings` + * @return See `librecrypt_make_settings` + * @throws See `librecrypt_make_settings` + * + * This function shall be MT-Safe but may be AS-Safe + */ + 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); + + /** + * Expected argument for the `lut` parameter + * of the `librecrypt_encode` function + */ + const char *encoding_lut; + + /** + * Expected argument for the `lut` parameter + * of the `librecrypt_decode` function + */ + const unsigned char *decoding_lut; + + /** + * The algoritm's hash result size, in number + * of bytes when using binary encoding + */ + size_t hash_size; + + /** + * Expected argument for the `strict_pad` parameter + * of the `librecrypt_decode` function + */ + int strict_pad; + + /** + * Expected argument for the `pad` parameter + * of the `librecrypt_decode` function + */ + char pad; +}; + +/** + * 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) + +/** + * Used at the end of `librecrypt_algorithms_` + */ +#define END_OF_ALGORITHMS {NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, '\0'} + +/** + * Create a concatenation of `ALPHABET` repeated + * for times; used to convert a base-64 alphabet + * to an encoding lookup table + * + * @param ALPHABET:string-literal A 64-character string + * @return :string-literal A 256-character string + * + * @seealso NONSTRING + */ +#define MAKE_ENCODING_LUT(ALPHABET) ALPHABET ALPHABET ALPHABET ALPHABET + +/** + * Use in algorithms's base-64 decoding lookup tables + * in entries matching the pad character and invalid + * characters + */ +#define XX 0xFFu + + +/** + * The list of all supported algorithms (those not disabled + * at compile-time) + * + * They are ordered by how preferable they are as the + * default algorithm, with the most preferable first + * + * The list is terminated by `END_OF_ALGORITHMS`, + * which can be checked using `IS_END_OF_ALGORITHMS` + */ +extern struct algorithm librecrypt_algorithms_[]; + +/** + * This just points to memset(3), but the pointer is volalite + * so that the compiler cannot assume that, and therefore the + * call cannot be optimised away + */ +extern void *(*volatile librecrypt_explicit_memset_____)(void *, int, size_t); /* librecrypt_wipe.c */ + +/** + * The is a pointer to a function that doesn't do anything, + * however the pointer is volatile so that the function + * cannot assume that and cannot optimise away the calculating + * of the input value + */ +extern void (*volatile librecrypt_explicit_____)(unsigned char); /* librecrypt_equal_binary.c */ + + +/** + * This function implements `librecrypt_hash_binary`, `librecrypt_hash`, + * and `librecrypt_test_supported`, depending on the value of `action`, + * see documentation of those functions for more default information + * + * @param out_buffer Output buffer for the hash result + * @param size The number bytes the function may write to `out_buffer` + * @param phrase The password to hash, may contain NUL bytes + * @param len The number of bytes in `phrase` + * @param settings The password hash configuration string, + * may contain resulting hash, which will be ignored + * @param reserved Reserved for future use, should be `NULL` + * @param action The function this function shall implement + * @return The number of bytes that would have been written to `out_buffer` + * if `size` was sufficiently large, excluding a terminating + * NUL byte (not present if `action == BINARY_HASH`); + * -1 on failure + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) +LIBRECRYPT_READ_STR__(6) LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ HIDDEN +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); + + +/** + * Default random number generator, used if no other + * is specified + * + * Generates up to `n`, or {SSIZE_MAX} if `n` is + * greater than {SSIZE_MAX}, random bytes + * + * @param out Output buffer for the random bytes + * @param n The maximum number of bytes to generate + * @param user Not used + * @return The number of generated bytes; + * will always be positive + * + * This function cannot fail, however it will ignore + * any `EINTR`; if `EINTR` is encountered `errno` will + * be set to `EINTR` upon return, otherwise `errno` + * will remain unmodifed + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_WUR__ HIDDEN +ssize_t librecrypt_rng_(void *out, size_t n, void *user); + + +/** + * Generate a specific number of random bytes + * + * @param out Output buffer for the random bytes + * @param n The number of bytes to generate + * @param rng The random number generator to use, + * `librecrypt_rng_` will be used if `NULL`, + * see `librecrypt_rng_` details, but not + * that it may return -1 on failure and has + * no restrictions on modifying `errno` + * @param user Passed as is to `*rng` as its third argument, + * so that it can be used for user-defined + * purposes + * @return 0 on success, -1 on failure + * + * When `rng` is non-`NULL`, this function inherits any + * MT-Unsafe and AS-Unsafe properties from `*rng`, being + * is MT-Safe and AS-Safe as a baseline; however when + * `rng` is `NULL`, this function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_WUR__ HIDDEN +int librecrypt_fill_with_random_(void *out, size_t n, ssize_t (*rng)(void *out, size_t n, void *user), void *user); + + +/** + * Find the first algorithm specified by a password has string + * + * @param settings The password has string + * @param len The number of bytes in `settings` + * @return Pointer to the algorithm information, + * `NULL` if not found + * + * This function does not modify `errno`, but the + * caller should usually set `errno` to `ENOSYS` + * if this function returns `NULL` + * + * This function is MT-Safe And AS-Safe + */ +LIBRECRYPT_READ_MEM__(1, 2) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ HIDDEN +const struct algorithm *librecrypt_find_first_algorithm_(const char *settings, size_t len); + + + +#ifdef TEST +# ifdef __linux__ +# include +# endif +# include +# include +# include +# include +# include +# include +# include +# include + +# define SET_UP_ALARM()\ + do {\ + unsigned int alarm_time__ = alarm(10u);\ + if (alarm_time__ > 10u)\ + alarm(alarm_time__);\ + } while (0) + +# if defined(PR_SET_DUMPABLE) +# define INIT_TEST_ABORT()\ + do {\ + struct rlimit rl__;\ + rl__.rlim_cur = 0;\ + rl__.rlim_max = 0;\ + (void) setrlimit(RLIMIT_CORE, &rl__);\ + (void) prctl(PR_SET_DUMPABLE, 0);\ + EXPECT_ABORT(abort());\ + } while (0) +# else +# define INIT_TEST_ABORT()\ + do {\ + struct rlimit rl__;\ + rl__.rlim_cur = 0;\ + rl__.rlim_max = 0;\ + (void) setrlimit(RLIMIT_CORE, &rl__);\ + EXPECT_ABORT(abort());\ + } while (0) +# endif + +# define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\ + do {\ + pid_t pid__;\ + int status__;\ + pid__ = fork();\ + EXPECT(pid__ != -1);\ + if (pid__ == 0) {\ + (EXPR);\ + _exit(0);\ + }\ + EXPECT(waitpid(pid__, &status__, 0) == pid__);\ + EXPECT(HOW(status__));\ + EXPECT(RETEXTRACT(status__) == RETEXPECT);\ + } while (0) + +# define EXPECT_ABORT(EXPR)\ + do {\ + EXPECT__(EXPR, WIFSIGNALED, WTERMSIG, SIGABRT);\ + } while (0) + +# define EXPECT(EXPR)\ + do {\ + if (!(EXPR)) {\ + fprintf(stderr, "Failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + exit(1);\ + }\ + } while (0) +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..f4adf12 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = +LDFLAGS = diff --git a/librecrypt.h b/librecrypt.h new file mode 100644 index 0000000..9cf966b --- /dev/null +++ b/librecrypt.h @@ -0,0 +1,776 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBRECRYPT_H +#define LIBRECRYPT_H + +#include +#include +#include +#include + + +#if defined(__GNUC__) +# define LIBRECRYPT_PURE__ __attribute__((__pure__)) +# 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__ +# define LIBRECRYPT_READ_STR__(S) +# define LIBRECRYPT_READ_MEM__(B, N) +# define LIBRECRYPT_WRITE_MEM__(B, N) +# define LIBRECRYPT_READ_WRITE_STR__(S) +#endif +#define LIBRECRYPT_NONNULL_1__ LIBRECRYPT_NONNULL_I__(1) + + +/** + * Symbol used as a general delimiter + */ +#define LIBRECRYPT_HASH_COMPOSITION_DELIMITER '$' + +/** + * Symbol used to delimit algorithms in a chain + */ +#define LIBRECRYPT_ALGORITHM_LINK_DELIMITER '>' + + + +/** + * 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 + * + * 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; +} + + +/** + * Get the number of chained hash algorithms specified in + * a password hash string + * + * @param hash The password hash string; must not be `NULL` + * @return The number of chained hash algorithms; always non-zero + * + * @seealso librecrypt_decompose_chain + * @seealso librecrypt_decompose_chain1 + * @seealso librecrypt_next_algorithm + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ LIBRECRYPT_PURE__ +inline size_t +librecrypt_chain_length(const char *hash) +{ + size_t n = 1u; + + for (; *hash; hash++) + if (*hash == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + n += 1u; + + return n; +} + + +/** + * Decompose the chain of hash algorithms specified in + * a password hash string + * + * @param hash The password hash string to decompose; may be modified; must not be `NULL` + * @param chain_out_array Output array for the hash algorithms; each element will be an offset of `hash` + * @param size The number of elements `chain_out_buffer` fit + * @return The number of chained hashes (same as `librecrypt_chain_length(hash)`) + * + * Algorithms are delimited using `LIBRECRYPT_ALGORITHM_LINK_DELIMITER` + * (which is `'>'`). The first `size - 1u` occurences will be set to `NULL`. + * `hash` can be restored by setting the terminating NUL byte in each but + * the last string, that is stored to `chain_out_array`, to + * `LIBRECRYPT_ALGORITHM_LINK_DELIMITER` + * + * If positive `size` is smaller than the returned value, + * `chain_out_array[size - 1u]` will contain all algorithms + * (still delimited by `LIBRECRYPT_ALGORITHM_LINK_DELIMITER`) + * that where not placed in earlier slots + * + * Unless already stripped out before input, + * `chain_out_array[size - 1u]` (provided that `size` is + * positive) will end with the resulting hash + * + * @seealso librecrypt_chain_length + * @seealso librecrypt_decompose_chain1 + * @seealso librecrypt_next_algorithm + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_WRITE_STR__(1) LIBRECRYPT_NONNULL_1__ LIBRECRYPT_WUR__ +inline size_t +librecrypt_decompose_chain(char *hash, char **chain_out_array, size_t size) +{ + size_t n = 1u; + + if (size) + chain_out_array[0u] = hash; + + for (; *hash; hash++) { + if (*hash == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { + if (n < size) { + *hash = '\0'; + chain_out_array[n] = &hash[1u]; + } + n += 1u; + } + } + + return n; +} + + +/** + * Replace each `LIBRECRYPT_ALGORITHM_LINK_DELIMITER` + * (which is `'>'`) in a password hash string with NUL + * bytes, effectively decomposing it to one string per + * hash algorithm, with the hashing result still attached + * to the last one if it as in the string + * + * @param hash The password hash string to decompose; may be modified; must not be `NULL` + * @return The number of chained hashes (same as `librecrypt_chain_length(hash)`) + * + * @seealso librecrypt_decompose_chain + * @seealso librecrypt_next_algorithm + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_WRITE_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ +inline size_t +librecrypt_decompose_chain1(char *hash) +{ + size_t n = 1u; + + for (; *hash; hash++) { + if (*hash == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { + *hash = '\0'; + n += 1u; + } + } + + return n; +} + + +/** + * This function is called repeatedly to get each + * hash algorithm (with parameters) that shall + * be chained together according to a provided + * hash string + * + * @param hash On the initial call, this argument shall point + * to the password hash string; on each call the + * function will update it to the current parsing + * state, meaning it will set `*hash` to point to + * an offset of `*hash` or point to `NULL`; + * additionally at each call, except on the final + * call, the string will be modified + * @return On the first call, this will be the first hash + * algorithm to use, and the second call it will + * be the second algorithm to use (if there is a + * second one), and so on. Once all algorithms have + * been extracted and returned, `NULL` is returned. + * + * Except once the function has returned `NULL`, overwriting + * the terminating NUL byte in the previous returned string + * with `LIBRECRYPT_ALGORITHM_LINK_DELIMITER` (which is `'>'`) + * will restore the password hash string to its original content + * + * Unless already stripped out before input, the last returned + * string will end with the resulting hash + * + * @seealso librecrypt_decompose_chain + * @seealso librecrypt_decompose_chain1 + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ +inline char * +librecrypt_next_algorithm(char **hash) +{ + char *ret; + + if (!*hash) + return NULL; + + ret = *hash; + *hash = strchr(*hash, LIBRECRYPT_ALGORITHM_LINK_DELIMITER); + if (*hash) + *(*hash)++ = '\0'; + return ret; +} + + +/** + * Encode a hash result or salt in ASCII, from binary + * + * This is the inverse of `librecrypt_decode` + * + * @param out_buffer Output buffer for the ASCII representation + * @param size The number bytes the function may write to `out_buffer` + * @param binary The raw binary data to encode + * @param len The number of bytes in `binary` + * @param lut The encoding alphabet, consisting of 64 characters, + * repeated 4 times + * @param pad The padding character to use at the end; the NUL byte if none + * @return The number of bytes that would have been written to `out_buffer`, + * excluding the terminating NUL byte, if `size` was sufficiently large + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and 1 in + * excess of the return value (this count includes the + * terminating NUL byte) + * + * On successful completion, if `size` is positive, + * the output to `out_buffer` is NUL terminated even + * if this truncates the string + * + * @seealso librecrypt_get_encoding + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ +size_t librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len, + const char lut[restrict static 256], char pad); + + +/** + * Encode a hash result or salt in ASCII, from binary + * + * This is the inverse of `librecrypt_decode` + * + * @param out_buffer Output buffer for the raw binary data + * @param size The number bytes the function may write to `out_buffer` + * @param ascii The ASCII encoding of the data, the text to decode + * @param len The number of bytes in `ascii` + * @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 + * @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 + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and the + * return value + * + * @seealso librecrypt_get_encoding + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ +ssize_t librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, + const unsigned char lut[restrict static 256], char pad, int strict_pad); + + +/** + * Get the ASCII (base-64) encoding used for the + * last algorithm in a chain + * + * @param settings The algorithm chain + * @param len The number of bytes in `settings` + * @param pad_out Output parameter for the padding character, + * will be set to the NUL byte if there is none + * @param strict_pad_out Set to 1 if encodings must be padded to a + * multiple of 4 bytes using `*pad_out` at the end, + * 0 otherwise; set arbitrarily if `*pad_out` is + * set to the NUL byte + * @param decoding 0 if the returned pointer should be useful for, + * `librecrypt_encode`, otherwise it will be useful + * for `librecrypt_decode` + * @return If `decoding` is 0: + * the encoding alphabet, consisting of 64 characters, + * repeated 4 times; + * otherwise: + * alphabet reverse lookup table, mapping any valid + * character (except the padding character) to the + * 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 + * + * The return type is `const char *` if `decoding` is 0, + * and `const unsigned char *` otherwise + * + * `*pad_out` shall be used for the `pad` parameter of the `librecrypt_encode` + * and `librecrypt_decode` functions, however the NUL byte may also be used + * for the `librecrypt_encode` function if `*strict_pad_out` is set to 0 + * + * `*strict_pad_out` shall be used for the `strict_pad` parameter of the + * `librecrypt_decode` function + * + * The returned pointer (unless `NULL`) shall be used for the `lut` parameter + * of the `librecrypt_encode` function if `decoding` is 0, and for the `lut` + * parameter of the `librecrypt_decode` function otherwise + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ +const void *librecrypt_get_encoding(const char *settings, size_t len, char *pad_out, + int *strict_pad_out, int decoding); + + +/** + * Securely erase a memory buffer + * + * @param buffer The memory to erase + * @param size The number of bytes in `buffer` to erase + * + * @seealso librecrypt_wipe_str + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) +void librecrypt_wipe(void *buffer, size_t size); + + +/** + * Securely erase a string + * + * @param string The string to erase + * + * @seealso librecrypt_wipe + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_WRITE_STR__(1) +inline void +librecrypt_wipe_str(char *string) +{ + if (string) + librecrypt_wipe(string, strlen(string)); +} + + +/** + * Compare two memory segments in constant-time + * + * @param a One of the segments to compare + * @param b The other segments to compare + * @param len The number of bytes to compare + * @return 1 if the `a` and `b` are equal in their + * first `len` bytes, 0 otherwise + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_MEM__(1, 3) LIBRECRYPT_READ_MEM__(2, 3) LIBRECRYPT_WUR__ +int librecrypt_equal_binary(const void *a, const void *b, size_t len); + + +/** + * Compare two strings in constant-time + * + * If the strings are of different length, + * it will stop immediately after measurement; + * this should not happen in the real world as + * this function is used to compare password + * hashes, whose length depends strictly on the + * configuration, not the input password + * + * @param a One of the strings to compare + * @param b The other string to compare + * @return 1 if the `a` and `b` are equal, 0 otherwise + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_STR__(1) LIBRECRYPT_READ_STR__(2) LIBRECRYPT_WUR__ LIBRECRYPT_NONNULL__ +inline int +librecrypt_equal(const char *a, const char *b) +{ + size_t n = strlen(a); + size_t m = strlen(b); + return n == m && librecrypt_equal_binary(a, b, n); +} + + +/** + * 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) + * + * @param out_buffer Output buffer for the ASCII representation + * @param size The number bytes the function may write to `out_buffer` + * @param settings The string to replace the asterisk-encoding in + * (the asterisk-free version is written to `out_buffer`) + * @param rng Random number generating function, `NULL` can be used + * for a default cryptographic random number generator; + * the function shall generate between 1 and `n` (inclusively) + * bytes and write them to the front of `out`, and return + * the number of generated bytes, or -1 on failure + * @param user Passed as the third argument to `*rng` for user-defined + * purposes, ignored if `rng` is `NULL` + * @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 + * + * If `rng` is `NULL`, any encountered `EINTR` is ignored, + * however, if it is encountered `errno` will be set to `EINTR`, + * but `errno` will remain unmodified otherwise, except if + * the function fails due to one of the above listed reasons + * + * Failure can only happen through `*rng`, it is + * responsible for setting `errno` if desired, or + * because of the conditions listed above` + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and 1 in + * excess of the return value (this count includes the + * terminating NUL byte) + * + * On successful completion, if `size` is positive, + * the output to `out_buffer` is NUL terminated even + * if this truncates the string + * + * On failure, `out_buffer` may be partially written + * + * The encoding depend on the algorithm, which is why + * it can fail with `EINVAL` or `ENOSYS` + * + * When `rng` is non-`NULL`, this function inherits any + * MT-Unsafe and AS-Unsafe properties from `*rng`, being + * is MT-Safe and AS-Safe as a baseline; however when + * `rng` is `NULL`, this function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_STR__(3) LIBRECRYPT_WUR__ +ssize_t librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *settings, + ssize_t (*rng)(void *out, size_t n, void *user), void *user); + + +/** + * Generate a password hash setting string + * + * @param out_buffer Output buffer for the ASCII representation + * @param size The number bytes the function may write to `out_buffer` + * @param algorithm Hash algorithm string; it need not specify any parameters + * however any unspecified parameter that cannot be determined + * by `memcost` or `timecost` will be set to an arbitrary, + * valid value. If `NULL`, a default, strong algorithm is + * selected. Note that the empty string represents the + * deprecated DES algorithm. Algorithm chains are disallowed. + * @param memcost Approximate memory cost in bytes, ignored if not supported; + * a default value is selected if 0 is specified + * @param timecost Total time cost, in an arbitrary, algorithm-dependent, + * implementation-dependent, monotonic (may have flat sections) + * unit and scale that is approximately linear; a default value + * is selected if 0 is specified + * @param gensalt Zero if salts shall specified to be randomly generated + * (reusable password hash setting string), + * non-zero if random salts shall be included in the generated + * output (non-reusable password hash setting string) + * @param rng Random number generating function, `NULL` can be used + * for a default cryptographic random number generator; + * the function shall generate between 1 and `n` (inclusively) + * bytes and write them to the front of `out`, and return + * the number of generated bytes, or -1 on failure; + * ignored if `gensalt` is zero + * @param user Passed as the third argument to `*rng` for user-defined + * 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 + * + * If `rng` is `NULL`, any encountered `EINTR` is ignored, + * however, if it is encountered `errno` will be set to `EINTR`, + * but `errno` will remain unmodified otherwise, except if + * the function fails due to `EINVAL` or `ENOSYS` + * + * Failure can only happen through `*rng`, it is + * responsible for setting `errno` if desired, or + * because of the conditions for `EINVAL` or `ENOSYS` + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and 1 in + * excess of the return value (this count includes the + * terminating NUL byte) + * + * On successful completion, if `size` is positive, + * the output to `out_buffer` is NUL terminated even + * if this truncates the string + * + * On failure, `out_buffer` may be partially written + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_STR__(3) LIBRECRYPT_WUR__ +ssize_t librecrypt_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); + + +/** + * Compute the hash of a password + * + * The hash will be stored in raw binary format without any settings + * + * @param out_buffer Output buffer for the hash result + * @param size The number bytes the function may write to `out_buffer` + * @param phrase The password to hash, may contain NUL bytes + * @param len The number of bytes in `phrase` + * @param settings The password hash configuration string, + * may contain resulting hash, which will be ignored + * @param reserved Reserved for future use, should be `NULL` + * @return The number of bytes that would have been written to `out_buffer` + * if `size` was sufficiently large; -1 on failure + * + * @throws EINVAL `reserved` is non-`NULL` (this case will be removed + * once `reserved` as being used by the library) + * @throws EINVAL `settings` is invalid (invalid algorithm configuration, + * invalid configuration syntax, or the output from one + * chained hash algorithm cannot be input the next algorithm + * in the chain (either because of format or length issues)) + * @throws EINVAL `settings` uses asterisk-encoding to specify random salts + * @throws ERANGE `len` is too large or too small for the the selected + * initial algorithm in the algorithm chain + * @throws ENOMEM Failed to allocate internal scratch memory + * @throws ENOSYS A selected hash algorithm is either not recognised + * disabled at compile-time + * + * Any encountered `EINTR` is ignored + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and the + * return value + * + * On failure, `out_buffer` will remain unmodified + * + * @seealso librecrypt_hash + * @seealso librecrypt_crypt + * @seealso librecrypt_test_supported + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_READ_STR__(5) +LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ +ssize_t librecrypt_hash_binary(char *restrict out_buffer, size_t size, const char *phrase, size_t len, + const char *settings, void *reserved); + + +/** + * Compute the hash of a password + * + * The hash will be encoded with `librecrypt_encode` + * but stored without any settings + * + * @param out_buffer Output buffer for the hash result + * @param size The number bytes the function may write to `out_buffer` + * @param phrase The password to hash, may contain NUL bytes + * @param len The number of bytes in `phrase` + * @param settings The password hash configuration string, + * may contain resulting hash, which will be ignored + * @param reserved Reserved for future use, should be `NULL` + * @return The number of bytes that would have been written to `out_buffer` + * if `size` was sufficiently large, excluding a terminating + * NUL byte; -1 on failure + * + * @throws EINVAL `reserved` is non-`NULL` (this case will be removed + * once `reserved` as being used by the library) + * @throws EINVAL `settings` is invalid (invalid algorithm configuration, + * invalid configuration syntax, or the output from one + * chained hash algorithm cannot be input the next algorithm + * in the chain (either because of format or length issues)) + * @throws EINVAL `settings` uses asterisk-encoding to specify random salts + * @throws ERANGE `len` is too large or too small for the the selected + * initial algorithm in the algorithm chain + * @throws ENOMEM Failed to allocate internal scratch memory + * @throws ENOSYS A selected hash algorithm is either not recognised + * disabled at compile-time + * + * Any encountered `EINTR` is ignored + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and 1 in + * excess of the return value (this count includes the + * terminating NUL byte) + * + * On successful completion, if `size` is positive, + * the output to `out_buffer` is NUL terminated even + * if this truncates the string + * + * On failure, `out_buffer` will remain unmodified + * + * @seealso librecrypt_hash_binary + * @seealso librecrypt_crypt + * @seealso librecrypt_test_supported + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_READ_STR__(5) +LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ +ssize_t librecrypt_hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len, + const char *settings, void *reserved); + + +/** + * Compute the hash of a password + * + * The hash will be encoded with `librecrypt_encode`; + * and the settings will be included in the front + * + * @param out_buffer Output buffer for the hash result + * @param size The number bytes the function may write to `out_buffer` + * @param phrase The password to hash, may contain NUL bytes + * @param len The number of bytes in `phrase` + * @param settings The password hash configuration string, + * may contain resulting hash, which will be ignored + * @param reserved Reserved for future use, should be `NULL` + * @return The number of bytes that would have been written to `out_buffer` + * if `size` was sufficiently large, excluding a terminating + * NUL byte; -1 on failure + * + * @throws EINVAL `reserved` is non-`NULL` (this case will be removed + * once `reserved` as being used by the library) + * @throws EINVAL `settings` is invalid (invalid algorithm configuration, + * invalid configuration syntax, or the output from one + * chained hash algorithm cannot be input the next algorithm + * in the chain (either because of format or length issues)) + * @throws ERANGE `len` is too large or too small for the the selected + * initial algorithm in the algorithm chain + * @throws ENOMEM Failed to allocate internal scratch memory + * @throws ENOSYS A selected hash algorithm is either not recognised + * disabled at compile-time + * + * Any encountered `EINTR` is ignored + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and 1 in + * excess of the return value (this count includes the + * terminating NUL byte) + * + * On successful completion, if `size` is positive, + * the output to `out_buffer` is NUL terminated even + * if this truncates the string + * + * On failure, `out_buffer` may be partially written + * + * @seealso librecrypt_hash_binary + * @seealso librecrypt_hash + * @seealso librecrypt_test_supported + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_READ_STR__(5) +LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ +ssize_t librecrypt_crypt(char *restrict out_buffer, size_t size, const char *phrase, size_t len, + const char *settings, void *reserved); + + +/** + * Check whether a hash algorithm chain is supported, + * for the given input, and that each algorithm + * configuration is valid + * + * @param phrase The password to hash, may contain NUL bytes; + * may be `NULL` even if `len` is non-zero + * @param len The number of bytes in `phrase`, if `phrase` is `NULL`, + * the function will check that the specified number of + * bytes is supported as well as any byte sequence unless + * `text` is non-zero + * @param text Assume the password is valid UTF-8 text (without NUL bytes) + * 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 + * @return 1 if the configuration is supported, 0 otherwise, which + * means part of the string is invalid: the algorithm does + * not exist, supported was disabled at compile-time, or an + * algorithm it does not support the output of the previous + * algorithm in the chain as input; additionally a small + * number of legacy algorithms require the passphrase to + * be within a specific length range or be valid text, + * if the selected word does not match such constraints + * for the first algorithm in the chain, 0 is returned + * + * This function is MT-Safe and AS-Safe + */ +LIBRECRYPT_READ_STR__(4) LIBRECRYPT_NONNULL_I__(4) LIBRECRYPT_WUR__ +int librecrypt_test_supported(const char *phrase, size_t len, int text, const char *settings); + + +/** + * Chain togather another set of hash algorithms + * + * @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 + * hash result, a new hash result will be computed and + * any random number generation specification in `augment` + * will be realised + * @param augment Password hash setting string describing the additional + * hashing to perform; if it contains a hash result, that + * part will be ignored + * @param reserved Reserved for future use, should be `NULL` + * @return The number of bytes that would have been written to `out_buffer` + * if `size` was sufficiently large, excluding a terminating + * NUL byte; -1 on failure + * + * @throws EINVAL `reserved` is non-`NULL` (this case will be removed + * once `reserved` as being used by the library) + * @throws EINVAL `settings` is invalid (invalid algorithm configuration, + * invalid configuration syntax, or the output from one + * chained hash algorithm cannot be input the next algorithm + * in the chain (either because of format or length issues)) + * @throws ERANGE `len` is too large or too small for the the selected + * initial algorithm in the algorithm chain + * @throws ENOMEM Failed to allocate internal scratch memory + * @throws ENOSYS A selected hash algorithm is either not recognised + * disabled at compile-time + * + * On successful completion, the N bytes is written to + * `out_buffer` where N is the lesser of `size` and 1 in + * excess of the return value (this count includes the + * terminating NUL byte) + * + * On successful completion, if `size` is positive, + * the output to `out_buffer` is NUL terminated even + * if this truncates the string + * + * On failure, `out_buffer` will remain unmodified + * + * This function is MT-Safe but AS-Unsafe + */ +LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_WRITE_STR__(3) LIBRECRYPT_READ_STR__(4) +LIBRECRYPT_NONNULL_I__(3) LIBRECRYPT_NONNULL_I__(4) LIBRECRYPT_WUR__ +ssize_t librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend, + const char *restrict augment, void *reserved); + + +#endif diff --git a/librecrypt_add_algorithm.3 b/librecrypt_add_algorithm.3 new file mode 100644 index 0000000..77867e8 --- /dev/null +++ b/librecrypt_add_algorithm.3 @@ -0,0 +1,134 @@ +.TH LIBRECRYPT_ADD_ALGORITHM 3 LIBRECRYPT +.SH NAME +librecrypt_add_algorithm - Append an algorithm chain to a password hash string + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_add_algorithm\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP, + char *\fIaugend\fP, const char *restrict \fIaugment\fP, + void *\fIreserved\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_add_algorithm () +function chains together another set of hash algorithms. +.PP +The +.I augend +argument is the existing password hash string. +If it already contains a hash result, +a new hash result will be computed and +any random number generation specification in +.I augment +will be realised. +.PP +The +.I augment +argument is a password hash setting string describing +the additional hashing to perform. +If it contains a hash result, that part is ignored. +.PP +The +.I reserved +parameter is reserved for future use and should be +.IR NULL . +.PP +On successful completion, if +.I size +is positive, the output is null byte-terminated even +if truncated. +.PP +On failure, +.I out_buffer +remains unmodified. +.PP +The +.I augend +and +.I augment +parameters must not be +.IR NULL . +The +.I out_buffer +parameter may only be +.I NULL +if if +.I size +is 0. + +.SH RETURN VALUES +The +.BR librecrypt_add_algorithm () +function returns the number of bytes that would have +been written to +.IR out_buffer +if +.I size +was sufficiently large, excluding the terminating +null byte. +On failure, -1 is returned and +.I errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_add_algorithm () +function will fail if: +.TP +.B EINVAL +.I reserved +is not +.IR NULL . +.TP +.B EINVAL +.I augend +or +.I augment +is invalid. +.TP +.B ERANGE +The selected initial algorithm requires a different password length. +.TP +.B ENOMEM +Failed to allocate internal scratch memory. +.TP +.B ENOSYS +A selected hash algorithm is not recognised or was disabled at compile-time. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_add_algorithm () +T} Thread safety MT-Safe +T{ +.BR librecrypt_add_algorithm () +T} Async-signal safety AS-Unsafe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_add_algorithm () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_crypt (3), +.BR librecrypt_make_settings (3), +.BR librecrypt_realise_salts (3) diff --git a/librecrypt_add_algorithm.c b/librecrypt_add_algorithm.c new file mode 100644 index 0000000..a2d8aa4 --- /dev/null +++ b/librecrypt_add_algorithm.c @@ -0,0 +1,96 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +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; + char *phrase, pad; + int strict_pad; + 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) + + if (!augend[prefix1]) { + prefix2 = librecrypt_settings_prefix(augment); + ret = prefix1 + 1u + prefix2; + if (size) { + COPY_PREFIX(); + min = prefix2 < size ? prefix2 : size; + memcpy(out_buffer, augment, min); + out_buffer[min] = '\0'; + } + return (ssize_t)ret; + } + + if (size <= prefix1 + 2u) { + phrase = NULL; + phraselen = 0u; + } else { + lut = librecrypt_get_encoding(augend, strlen(augend), &pad, &strict_pad, 1); + if (!lut) + return -1; + + len = strlen(&augend[prefix1]); + r = librecrypt_decode(NULL, 0, &augend[prefix1], len, lut, pad, strict_pad); + if (r <= 0) { + if (!r) + abort(); + return -1; + } + phraselen = (size_t)r; + phrase = malloc(phraselen); + if (!phrase) + return -1; + if (librecrypt_decode(phrase, phraselen, &augend[prefix1], len, lut, pad, strict_pad) != r) + abort(); + } + + COPY_PREFIX(); + ret = prefix1 + 1u; + r = librecrypt_crypt(out_buffer, size + 1u, phrase, phraselen, augment, reserved); + if (r <= 0) { + librecrypt_wipe(phrase, phraselen); + free(phrase); + if (!r) + abort(); + return -1; + } + ret += (size_t)r; + + librecrypt_wipe(phrase, phraselen); + free(phrase); + return (ssize_t)ret; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_algorithms_.c b/librecrypt_algorithms_.c new file mode 100644 index 0000000..34c6b3f --- /dev/null +++ b/librecrypt_algorithms_.c @@ -0,0 +1,69 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#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,\ + .hash_size = ALGO##__HASH_SIZE,\ + .strict_pad = ALGO##__STRICT_PAD,\ + .pad = ALGO##__PAD\ + } + + +#define X(ALGO) IF__##ALGO##__SUPPORTED(ENTRY(ALGO) COMMA) +struct algorithm librecrypt_algorithms_[] = { + LIST_ALGORITHMS(X) + END_OF_ALGORITHMS +}; +#undef X + + +#else + + +int +main(void) +{ +#define X(ALGO) IF__##ALGO##__SUPPORTED(+ 1u) + const size_t count = 0u LIST_ALGORITHMS(X); +#undef X + size_t i; + + SET_UP_ALARM(); + + 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); + 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 != '*'); + } + } + assert(i == count); + CHECK(IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[count])); + + return 0; +} + + +#endif diff --git a/librecrypt_chain_length.3 b/librecrypt_chain_length.3 new file mode 100644 index 0000000..eb0b1cc --- /dev/null +++ b/librecrypt_chain_length.3 @@ -0,0 +1,69 @@ +.TH LIBRECRYPT_CHAIN_LENGTH 3 LIBRECRYPT +.SH NAME +librecrypt_chain_length - Get number of algorithms in a chained password hash string + +.SH SYNOPSIS +.nf +#include + +size_t \fBlibrecrypt_chain_length\fP(const char *\fIhash\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_chain_length () +function returns the number of hash algorithms +specified in the chain contained in +.IR hash . +Algorithms are delimited by +.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER +(which is +.BR "\(aq>\(aq" ). +.PP +.I hash +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_chain_length () +function returns the number of chained hash algorithms. +The return value is always non-zero. + +.SH ERRORS +The +.BR librecrypt_chain_length () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_chain_length () +T} Thread safety MT-Safe +T{ +.BR librecrypt_chain_length () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_chain_length () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_decompose_chain (3), +.BR librecrypt_decompose_chain1 (3), +.BR librecrypt_next_algorithm (3) diff --git a/librecrypt_chain_length.c b/librecrypt_chain_length.c new file mode 100644 index 0000000..64b9472 --- /dev/null +++ b/librecrypt_chain_length.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline size_t librecrypt_chain_length(const char *hash); + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + EXPECT(librecrypt_chain_length("") == 1u); + EXPECT(librecrypt_chain_length("a$b") == 1u); + EXPECT(librecrypt_chain_length(">") == 2u); + EXPECT(librecrypt_chain_length(">>") == 3u); + 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; +} + + +#endif diff --git a/librecrypt_crypt.3 b/librecrypt_crypt.3 new file mode 100644 index 0000000..0237862 --- /dev/null +++ b/librecrypt_crypt.3 @@ -0,0 +1,131 @@ +.TH LIBRECRYPT_CRYPT 3 LIBRECRYPT +.SH NAME +librecrypt_crypt - Compute password hash encoded in ASCII with settings prefix + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_crypt\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP, + const char *\fIphrase\fP, size_t \fIlen\fP, + const char *\fIsettings\fP, void *\fIreserved\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_crypt () +function computes the hash of a password. +The resulting hash is encoded with +.BR librecrypt_encode (3) +and the settings string is included in the front +of the output. +.PP +The password is provided in +.IR phrase +and can contain null bytes; its length is +specified, in bytes, by +.IR len . +.PP +The password hash configuration string is +provided in +.IR settings . +If +.I settings +contains a resulting hash, it is ignored. +.PP +The +.I reserved +parameter is reserved for future use and +should be +.IR NULL . +.PP +On successful completion, if +.I size +is positive, the output is +null byte-terminated even if truncated. +.PP +Any encountered +.BR EINTR +is ignored. +.PP +On failure, +.I out_buffer +may be partially written. +.PP +.I settings +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_crypt () +function returns the number of bytes that would +have been written to +.IR out_buffer +if +.I size +was sufficiently large, excluding the terminating +null byte. On failure, -1 is returned and +.I errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_crypt () +function will fail if: +.TP +.B EINVAL +.I reserved +is not +.IR NULL . +.TP +.B EINVAL +.I settings +is invalid. +.TP +.B ERANGE +.I len +is too large or too small for the selected +initial algorithm. +.TP +.B ENOMEM +Failed to allocate internal scratch memory. +.TP +.B ENOSYS +A selected hash algorithm is not recognised +or was disabled at compile-time. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_crypt () +T} Thread safety MT-Safe +T{ +.BR librecrypt_crypt () +T} Async-signal safety AS-Unsafe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_crypt () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_hash_binary (3), +.BR librecrypt_hash (3), +.BR librecrypt_test_supported (3) diff --git a/librecrypt_crypt.c b/librecrypt_crypt.c new file mode 100644 index 0000000..6192650 --- /dev/null +++ b/librecrypt_crypt.c @@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_crypt(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved) +{ + return librecrypt_hash_(out_buffer, size, phrase, len, settings, reserved, ASCII_CRYPT); +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_decode.3 b/librecrypt_decode.3 new file mode 100644 index 0000000..cab9aad --- /dev/null +++ b/librecrypt_decode.3 @@ -0,0 +1,104 @@ +.TH LIBRECRYPT_DECODE 3 LIBRECRYPT +.SH NAME +librecrypt_decode - Decode ASCII encoding of a salt or hash result into binary + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_decode\fP(void *\fIout_buffer\fP, size_t \fIsize\fP, + const char *\fIascii\fP, size_t \fIlen\fP, + const unsigned char \fIlut\fP[restrict static 256], + char \fIpad\fP, int \fIstrict_pad\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_decode () +function decodes +.I len +bytes of ASCII text from +.IR ascii +into raw binary data written to +.IR out_buffer . +This is used for decoding hash results and salts. +.PP +The +.I lut +argument is a reverse lookup table that maps any +valid character (except the padding character) to +its value in the encoding alphabet and maps any +invalid character to +.BR 0xFFu . +.PP +The +.I pad +argument specifies the padding character to use +at the end, or the null byte if none. +.PP +If +.I strict_pad +is non-zero, the padding at the end is mandatory. +.PP +On successful completion, up to +.I size +bytes are written to +.IR out_buffer . +The return value is the number of bytes that would +have been written if +.I size +was sufficiently large. + +.SH RETURN VALUES +The +.BR librecrypt_decode () +function returns the number of bytes that would +have been written to +.IR out_buffer +if +.I size +was sufficiently large. +On failure, -1 is returned and +.IR errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_decode () +function will fail if: +.TP +.B EINVAL +.I ascii +uses an invalid encoding. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_decode () +T} Thread safety MT-Safe +T{ +.BR librecrypt_decode () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_decode () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_encode (3), +.BR librecrypt_get_encoding (3) diff --git a/librecrypt_decode.c b/librecrypt_decode.c new file mode 100644 index 0000000..ee451a7 --- /dev/null +++ b/librecrypt_decode.c @@ -0,0 +1,257 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, + const unsigned char lut[restrict static 256], char pad, int strict_pad) +{ + const unsigned char *str = (const unsigned char *)ascii; + unsigned char *data = out_buffer; + unsigned char a, b, c, d; + size_t n = 0u; + + for(; len >= 4u; str += 4u) { + len -= 4u; + + a = lut[str[0u]]; + b = lut[str[1u]]; + if ((a | b) == 0xFFu) + goto einval; + if (n < size) + data[n] = (unsigned char)((a << 2) | (b >> 4)); + n++; + + c = lut[str[2u]]; + if (c == 0xFFu) { + if (len || !pad || str[2u] != pad || str[3u] != pad) + goto einval; + break; + } + if (n < size) + data[n] = (unsigned char)((b << 4u) | (c >> 2u)); + n++; + + d = lut[str[3u]]; + if (d == 0xFFu) { + if (len || !pad || str[3u] != pad) + goto einval; + break; + } + if (n < size) + data[n] = (unsigned char)((c << 6) | (d >> 0)); + n++; + } + + if (len && strict_pad && pad) + goto einval; + + switch (len) { + case 0u: + goto out; + case 1u: + goto einval; + + default: + a = lut[str[0u]]; + b = lut[str[1u]]; + if ((a | b) == 0xFFu) + goto einval; + if (n < size) + data[n] = (unsigned char)((a << 2) | (b >> 4)); + n++; + if (len == 2u) + break; + + c = lut[str[2u]]; + if (c == 0xFFu) { + if (!pad || str[2u] != pad) + goto einval; + break; + } + if (n < size) + data[n] = (unsigned char)((b << 4u) | (c >> 2u)); + n++; + break; + } + +out: + return (ssize_t)n; + +einval: + errno = EINVAL; + return -1; +} + + +#else + + +static const unsigned char 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 +}; + + +#define CHECK(BINARY, ASCII, PAD)\ + check((BINARY), sizeof(BINARY) - 1u, (ASCII PAD), sizeof(ASCII) - 1u, sizeof(ASCII PAD) - 1u) + + +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; + + assert(binary_len <= sizeof(buf)); + + assert(padded_len % 4u == 0u); + assert(padded_len / 4u == (unpadded_len + 3u) / 4u); + assert(padded_len >= unpadded_len); + + 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 { + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 0) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + 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); + + 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); + j = i < binary_len ? i : binary_len; + EXPECT(!memcmp(buf, binary, j)); + for (; j < sizeof(buf); j++) + EXPECT(buf[j] == 99); + + memset(buf, 99, sizeof(buf)); + EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '\0', 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); + + memset(buf, 99, sizeof(buf)); + EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '=', 0) == (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 (padded_len == unpadded_len) { + memset(buf, 99, sizeof(buf)); + EXPECT(librecrypt_decode(buf, i, ascii, unpadded_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); + } else { + errno = 0; + EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '=', 1) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '\0', 0) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '\0', 1) == -1); + EXPECT(errno == EINVAL); + } + + memset(buf, 99, sizeof(buf)); + EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 0) == (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); + + 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); + + } +} + + +int +main(void) +{ + int i; + + SET_UP_ALARM(); + + CHECK("", "", ""); + CHECK("\x00", "AA", "=="); + CHECK("\x00\x00", "AAA", "="); + CHECK("\x00\x00\x00", "AAAA", ""); + CHECK("12345678", "MTIzNDU2Nzg", "="); + CHECK("testtest", "dGVzdHRlc3Q", "="); + CHECK("zy[]y21 !", "enlbXXkyMSAh", ""); + CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw", "=="); + + for (i = 0; i <= 1; i++) { + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA=*", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA*=", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA**", 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", 1u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "====", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + } + + return 0; +} + + +#endif diff --git a/librecrypt_decompose_chain.3 b/librecrypt_decompose_chain.3 new file mode 100644 index 0000000..fc9f928 --- /dev/null +++ b/librecrypt_decompose_chain.3 @@ -0,0 +1,98 @@ +.TH LIBRECRYPT_DECOMPOSE_CHAIN 3 LIBRECRYPT +.SH NAME +librecrypt_decompose_chain - Split a chained password hash string into algorithm components + +.SH SYNOPSIS +.nf +#include + +size_t \fBlibrecrypt_decompose_chain\fP(char *\fIhash\fP, char **\fIchain_out_array\fP, size_t \fIsize\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_decompose_chain () +function decomposes the chain of hash algorithms +specified in +.IR hash +into null byte-terminated substrings. The +.IR hash +string is modified by replacing up to +.I size-1 +instances of +.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER +(which is +.BR \(aq>\(aq ) +by null bytes and storing pointers to the +start of each substring in +.IR chain_out_array . +.PP +If +.I size +is positive and smaller than the number of +algorithms, then +.I chain_out_array[size-1] +will contain the remainder of the chain +(still delimited by +.IR LIBRECRYPT_ALGORITHM_LINK_DELIMITER ). +.PP +Unless already stripped out before input, the +last substring ends with the resulting hash. +.PP +The original +.IR hash +string can be restored by overwriting each +terminating null byte in all but the last +substring stored in +.IR chain_out_array +with +.IR LIBRECRYPT_ALGORITHM_LINK_DELIMITER . +.PP +.I hash +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_decompose_chain () +function returns the number of chained hashes +(the same value as returned by +.BR librecrypt_chain_length (3)). + +.SH ERRORS +The +.BR librecrypt_decompose_chain () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_decompose_chain () +T} Thread safety MT-Safe +T{ +.BR librecrypt_decompose_chain () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_decompose_chain () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_chain_length (3), +.BR librecrypt_decompose_chain1 (3), +.BR librecrypt_next_algorithm (3) diff --git a/librecrypt_decompose_chain.c b/librecrypt_decompose_chain.c new file mode 100644 index 0000000..9b7aede --- /dev/null +++ b/librecrypt_decompose_chain.c @@ -0,0 +1,153 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline size_t librecrypt_decompose_chain(char *hash, char **chain_out_array, size_t size); + + +#else + + +#define HASH_1 "a$b" + +#define HASH_2_A "a$b" +#define HASH_2_B "c$d" +#define HASH_2_C "e$f" +#define HASH_2_BC HASH_2_B">"HASH_2_C +#define HASH_2 HASH_2_A">"HASH_2_BC + +#define HASH_3_A "" +#define HASH_3_B "a$b" +#define HASH_3_C "c$d" +#define HASH_3_D "e$f" +#define HASH_3_E "" +#define HASH_3_DE HASH_3_D">"HASH_3_E +#define HASH_3_CDE HASH_3_C">"HASH_3_DE +#define HASH_3_BCDE HASH_3_B">"HASH_3_CDE +#define HASH_3 HASH_3_A">"HASH_3_BCDE + + +#define NULL_OUT(ARRAY)\ + do {\ + for (i = 0u; i < ELEMSOF(ARRAY); i++)\ + (ARRAY)[i] = NULL;\ + } while (0) + + +int +main(void) +{ + char buf[64u]; + char *chain[10u]; + size_t i; + + SET_UP_ALARM(); + + stpcpy(buf, HASH_1); + EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 1u); + EXPECT(!strcmp(buf, HASH_1)); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 1u) == 1u); + EXPECT(!strcmp(buf, HASH_1)); + EXPECT(chain[0u] == buf); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, ELEMSOF(chain)) == 1u); + EXPECT(!strcmp(buf, HASH_1)); + EXPECT(chain[0u] == buf); + + stpcpy(buf, HASH_2); + EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 3u); + EXPECT(!strcmp(buf, HASH_2)); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 1u) == 3u); + EXPECT(!strcmp(buf, HASH_2)); + EXPECT(chain[0u] == buf); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 2u) == 3u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_2_A)); + EXPECT(!strcmp(chain[1u], HASH_2_BC)); + stpcpy(buf, HASH_2); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, ELEMSOF(chain)) == 3u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] != NULL); + EXPECT(chain[3u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_2_A)); + EXPECT(!strcmp(chain[1u], HASH_2_B)); + EXPECT(!strcmp(chain[2u], HASH_2_C)); + + stpcpy(buf, HASH_3); + EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 5u); + EXPECT(!strcmp(buf, HASH_3)); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 1u) == 5u); + EXPECT(!strcmp(buf, HASH_3)); + EXPECT(chain[0u] == buf); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 2u) == 5u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_3_A)); + EXPECT(!strcmp(chain[1u], HASH_3_BCDE)); + stpcpy(buf, HASH_3); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 3u) == 5u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] != NULL); + EXPECT(chain[3u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_3_A)); + EXPECT(!strcmp(chain[1u], HASH_3_B)); + EXPECT(!strcmp(chain[2u], HASH_3_CDE)); + stpcpy(buf, HASH_3); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 4u) == 5u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] != NULL); + EXPECT(chain[3u] != NULL); + EXPECT(chain[4u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_3_A)); + EXPECT(!strcmp(chain[1u], HASH_3_B)); + EXPECT(!strcmp(chain[2u], HASH_3_C)); + EXPECT(!strcmp(chain[3u], HASH_3_DE)); + stpcpy(buf, HASH_3); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 5u) == 5u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] != NULL); + EXPECT(chain[3u] != NULL); + EXPECT(chain[4u] != NULL); + EXPECT(chain[5u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_3_A)); + EXPECT(!strcmp(chain[1u], HASH_3_B)); + EXPECT(!strcmp(chain[2u], HASH_3_C)); + EXPECT(!strcmp(chain[3u], HASH_3_D)); + EXPECT(!strcmp(chain[4u], HASH_3_E)); + stpcpy(buf, HASH_3); + NULL_OUT(chain); + EXPECT(librecrypt_decompose_chain(buf, chain, 6u) == 5u); + EXPECT(chain[0u] != NULL); + EXPECT(chain[1u] != NULL); + EXPECT(chain[2u] != NULL); + EXPECT(chain[3u] != NULL); + EXPECT(chain[4u] != NULL); + EXPECT(chain[5u] == NULL); + EXPECT(!strcmp(chain[0u], HASH_3_A)); + EXPECT(!strcmp(chain[1u], HASH_3_B)); + EXPECT(!strcmp(chain[2u], HASH_3_C)); + EXPECT(!strcmp(chain[3u], HASH_3_D)); + EXPECT(!strcmp(chain[4u], HASH_3_E)); + + return 0; +} + + +#endif diff --git a/librecrypt_decompose_chain1.3 b/librecrypt_decompose_chain1.3 new file mode 100644 index 0000000..8a6e3b0 --- /dev/null +++ b/librecrypt_decompose_chain1.3 @@ -0,0 +1,75 @@ +.TH LIBRECRYPT_DECOMPOSE_CHAIN1 3 LIBRECRYPT +.SH NAME +librecrypt_decompose_chain1 - Replace algorithm link delimiters with null bytes + +.SH SYNOPSIS +.nf +#include + +size_t \fBlibrecrypt_decompose_chain1\fP(char *\fIhash\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_decompose_chain1 () +function replaces each +.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER +(which is +.BR \(aq>\(aq ) +in +.I hash +with a null byte. This effectively decomposes +the string into one string per hash algorithm. +.PP +Unless already stripped out before input, the +last resulting string ends with the resulting +hash, if it was present in the input. +.PP +.I hash +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_decompose_chain1 () +function returns the number of chained hashes +(the same value as returned by +.BR librecrypt_chain_length (3)). + +.SH ERRORS +The +.BR librecrypt_decompose_chain1 () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_decompose_chain1 () +T} Thread safety MT-Safe +T{ +.BR librecrypt_decompose_chain1 () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_decompose_chain1 () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_chain_length (3), +.BR librecrypt_decompose_chain (3), +.BR librecrypt_next_algorithm (3) diff --git a/librecrypt_decompose_chain1.c b/librecrypt_decompose_chain1.c new file mode 100644 index 0000000..718e54c --- /dev/null +++ b/librecrypt_decompose_chain1.c @@ -0,0 +1,37 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline size_t librecrypt_decompose_chain1(char *hash); + + +#else + + +#define CHECK(IN, OUT, N)\ + do {\ + assert(sizeof(IN) <= sizeof(buf));\ + assert(sizeof(IN) == sizeof(OUT));\ + stpcpy(buf, (IN));\ + n = librecrypt_decompose_chain1(buf);\ + EXPECT(n == (N));\ + EXPECT(n == librecrypt_chain_length(IN));\ + EXPECT(!memcmp(buf, (OUT), sizeof(IN)));\ + } while (0) + + +int +main(void) +{ + char buf[64u]; + size_t n; + SET_UP_ALARM(); + CHECK("", "", 1u); + CHECK(">", "\0", 2u); + CHECK("a$b>c$d>e$f", "a$b\0c$d\0e$f", 3u); + return 0; +} + + +#endif diff --git a/librecrypt_encode.3 b/librecrypt_encode.3 new file mode 100644 index 0000000..95862f8 --- /dev/null +++ b/librecrypt_encode.3 @@ -0,0 +1,93 @@ +.TH LIBRECRYPT_ENCODE 3 LIBRECRYPT +.SH NAME +librecrypt_encode - Encode binary salt or hash result into ASCII + +.SH SYNOPSIS +.nf +#include + +size_t \fBlibrecrypt_encode\fP(char *\fIout_buffer\fP, size_t \fIsize\fP, + const void *\fIbinary\fP, size_t \fIlen\fP, + const char \fIlut\fP[restrict static 256], char \fIpad\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_encode () +function encodes +.I len +bytes of binary data from +.IR binary +into an ASCII representation written to +.IR out_buffer . +This is used for encoding hash results and salts. +.PP +The +.I lut +argument is an encoding alphabet, consisting +of 64 characters, repeated 4 times. +.PP +The +.I pad +argument specifies the padding character to +use at the end, or the null byte if none. +.PP +On successful completion, up to +.I size +bytes are written to +.IR out_buffer ; +if +.I size +is positive, the output is always +null byte-terminated even if truncated. +.PP +The return value is the number of bytes +that would have been written, excluding +the terminating null byte, if +.I size +was sufficiently large. + +.SH RETURN VALUES +The +.BR librecrypt_encode () +function returns the number of bytes that +would have been written to +.IR out_buffer , +excluding the terminating null byte. + +.SH ERRORS +The +.BR librecrypt_encode () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_encode () +T} Thread safety MT-Safe +T{ +.BR librecrypt_encode () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_encode () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_decode (3), +.BR librecrypt_get_encoding (3) diff --git a/librecrypt_encode.c b/librecrypt_encode.c new file mode 100644 index 0000000..2199ac1 --- /dev/null +++ b/librecrypt_encode.c @@ -0,0 +1,177 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + + +#define O1(I1, I2, I3) ((I1) >> 2) +#define O2(I1, I2, I3) (((I1) << 4) | ((I2) >> 4)) +#define O3(I1, I2, I3) (((I2) << 2) | ((I3) >> 6)) +#define O4(I1, I2, I3) (I3) + + +size_t +librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len, + const char lut[restrict static 256], char pad) +{ + const unsigned char *data = binary; + unsigned char a, b, c; + size_t q, r, i, j, n; + + q = len / 3u; + r = len % 3u; + n = q * 4u + (!r ? 0u : pad ? 4u : r + 1u); + + if (!size) + return n; + + size -= 1u; + out_buffer[n < size ? n : size] = '\0'; + + if (r == 1u) { + a = data[q * 3u + 0u]; + switch (size > q * 4u ? size - q * 4u : 0u) { /* fall-through */ + default: + if (pad) + out_buffer[q * 4u + 3u] = pad; + case 3u: + if (pad) + out_buffer[q * 4u + 2u] = pad; + case 2u: + out_buffer[q * 4u + 1u] = lut[O2(a, 0, 0) & 255]; + case 1u: + out_buffer[q * 4u + 0u] = lut[O1(a, 0, 0) & 255]; + case 0u: + break; + } + } else if (r == 2u) { + a = data[q * 3u + 0u]; + b = data[q * 3u + 1u]; + switch (size > q * 4u ? size - q * 4u : 0u) { /* fall-through */ + default: + if (pad) + out_buffer[q * 4u + 3u] = pad; + case 3u: + out_buffer[q * 4u + 2u] = lut[O3(a, b, 0) & 255]; + case 2u: + out_buffer[q * 4u + 1u] = lut[O2(a, b, 0) & 255]; + case 1u: + out_buffer[q * 4u + 0u] = lut[O1(a, b, 0) & 255]; + case 0u: + break; + } + } + + i = q * 4u; + j = q * 3u; + if (i > size) { + q = size / 4u; + r = size % 4u; + i = q * 4u; + j = q * 3u; + c = data[j + 2u]; + b = data[j + 1u]; + a = data[j + 0u]; + switch (r) { /* fall-through */ + case 3u: + out_buffer[i + 2u] = lut[O3(a, b, c) & 255]; + case 2u: + out_buffer[i + 1u] = lut[O2(a, b, c) & 255]; + case 1u: + out_buffer[i + 0u] = lut[O1(a, b, c) & 255]; + default: + break; + } + } + while (i) { + i -= 4u; + j -= 3u; + c = data[j + 2u]; + b = data[j + 1u]; + a = data[j + 0u]; + out_buffer[i + 3u] = lut[O4(a, b, c) & 255]; + out_buffer[i + 2u] = lut[O3(a, b, c) & 255]; + out_buffer[i + 1u] = lut[O2(a, b, c) & 255]; + out_buffer[i + 0u] = lut[O1(a, b, c) & 255]; + } + + return n; +} + + +#else + + +NONSTRING static const char lut[256u] = MAKE_ENCODING_LUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + + +#define CHECK(BINARY, ASCII)\ + check((BINARY), sizeof(BINARY) - 1u, (ASCII), sizeof(ASCII) - 1u) + + +static void +check(const char *binary, size_t binary_len, const char *ascii, size_t ascii_len) +{ + size_t padded_ascii_len = ascii_len; + char buf[256u]; + size_t i, j, n; + + if (padded_ascii_len & 3u) { + padded_ascii_len |= 3u; + padded_ascii_len += 1u; + } + assert(padded_ascii_len % 4u == 0u); + assert(padded_ascii_len / 4u == (ascii_len + 3u) / 4u); + assert(padded_ascii_len >= ascii_len); + + EXPECT(librecrypt_encode(NULL, 0u, binary, binary_len, lut, '\0') == ascii_len); + EXPECT(librecrypt_encode(buf, 0u, binary, binary_len, lut, '\0') == ascii_len); + + EXPECT(librecrypt_encode(NULL, 0u, binary, binary_len, lut, '=') == padded_ascii_len); + EXPECT(librecrypt_encode(buf, 0u, binary, binary_len, lut, '=') == padded_ascii_len); + + 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); + EXPECT(!memcmp(buf, ascii, i)); + EXPECT(buf[i] == '\0'); + for (j = i + 1u; j < sizeof(buf); j++) + EXPECT(buf[j] == 99); + } + + 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); + j = i < ascii_len ? i : ascii_len; + n = i < padded_ascii_len ? i : padded_ascii_len; + EXPECT(!memcmp(buf, ascii, j)); + for (; j < n; j++) + EXPECT(buf[j] == '='); + EXPECT(buf[j++] == '\0'); + for (; j < sizeof(buf); j++) + EXPECT(buf[j] == 99); + } +} + + +int +main(void) +{ + SET_UP_ALARM(); + CHECK("", ""); + CHECK("\x00", "AA"); + CHECK("\x00\x00", "AAA"); + CHECK("\x00\x00\x00", "AAAA"); + CHECK("12345678", "MTIzNDU2Nzg"); + CHECK("testtest", "dGVzdHRlc3Q"); + CHECK("zy[]y21 !", "enlbXXkyMSAh"); + CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw"); + return 0; +} + + +#endif diff --git a/librecrypt_equal.3 b/librecrypt_equal.3 new file mode 100644 index 0000000..cc3a387 --- /dev/null +++ b/librecrypt_equal.3 @@ -0,0 +1,70 @@ +.TH LIBRECRYPT_EQUAL 3 LIBRECRYPT +.SH NAME +librecrypt_equal - Compare strings in constant time + +.SH SYNOPSIS +.nf +#include + +int \fBlibrecrypt_equal\fP(const char *\fIa\fP, const char *\fIb\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_equal () +function compares two null byte-terminated strings +in constant time. +.PP +If the strings are of different length, the function +returns 0 after the lengths have been measured. +This should not happen when comparing password hashes, +since their length is determined by the selected +configuration. +.PP +.I a +and +.I b +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_equal () +function returns 1 if the strings are equal, +and 0 otherwise. + +.SH ERRORS +The +.BR librecrypt_equal () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_equal () +T} Thread safety MT-Safe +T{ +.BR librecrypt_equal () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_equal () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_equal_binary (3) diff --git a/librecrypt_equal.c b/librecrypt_equal.c new file mode 100644 index 0000000..22e2c05 --- /dev/null +++ b/librecrypt_equal.c @@ -0,0 +1,39 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline int librecrypt_equal(const char *a, const char *b); + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + EXPECT(librecrypt_equal("", "") == 1); + EXPECT(librecrypt_equal("", "a") == 0); + EXPECT(librecrypt_equal("a", "") == 0); + EXPECT(librecrypt_equal("a", "b") == 0); + EXPECT(librecrypt_equal("a", "a") == 1); + EXPECT(librecrypt_equal("abcdef", "Abcdef") == 0); + EXPECT(librecrypt_equal("abcdef", "ABcdef") == 0); + EXPECT(librecrypt_equal("abcdef", "aBcdef") == 0); + EXPECT(librecrypt_equal("abcdef", "abCdef") == 0); + EXPECT(librecrypt_equal("abcdef", "abCdeF") == 0); + EXPECT(librecrypt_equal("abcdef", "abcdeF") == 0); + EXPECT(librecrypt_equal("abcdef", "abcdex") == 0); + EXPECT(librecrypt_equal("abcdef", "abcxef") == 0); + EXPECT(librecrypt_equal("abcdef", "xbcdef") == 0); + EXPECT(librecrypt_equal("abcdef", "abcdef") == 1); + EXPECT(librecrypt_equal("abcdef", "abcdefg") == 0); + EXPECT(librecrypt_equal("abcdefg", "abcdef") == 0); + EXPECT(librecrypt_equal("abcdef", "") == 0); + EXPECT(librecrypt_equal("", "abcdef") == 0); + return 0; +} + + +#endif diff --git a/librecrypt_equal_binary.3 b/librecrypt_equal_binary.3 new file mode 100644 index 0000000..d441c5b --- /dev/null +++ b/librecrypt_equal_binary.3 @@ -0,0 +1,65 @@ +.TH LIBRECRYPT_EQUAL_BINARY 3 LIBRECRYPT +.SH NAME +librecrypt_equal_binary - Compare memory segments in constant time + +.SH SYNOPSIS +.nf +#include + +int \fBlibrecrypt_equal_binary\fP(const void *\fIa\fP, const void *\fIb\fP, size_t \fIlen\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_equal_binary () +function compares the first +.I len +bytes of +.I a +and +.I b +in constant time. + +.SH RETURN VALUES +The +.BR librecrypt_equal_binary () +function returns 1 if the memory segments are +equal in their first +.I len +bytes, and 0 otherwise. + +.SH ERRORS +The +.BR librecrypt_equal_binary () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_equal_binary () +T} Thread safety MT-Safe +T{ +.BR librecrypt_equal_binary () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_equal_binary () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_equal (3) diff --git a/librecrypt_equal_binary.c b/librecrypt_equal_binary.c new file mode 100644 index 0000000..e1d17cd --- /dev/null +++ b/librecrypt_equal_binary.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wsuggest-attribute=const" +# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + + +static void +explicit(unsigned char r) +{ + (void) r; +#if defined(__GNUC__) + __asm__ volatile ("" :: "g" (r) : "memory"); +#endif +} + + +void (*volatile librecrypt_explicit_____)(unsigned char) = &explicit; + + +int +librecrypt_equal_binary(const void *a, const void *b, size_t len) +{ + const unsigned char *x = a; + const unsigned char *y = b; + size_t i; + unsigned char r = 0u; + + for (i = 0u; i < len; i++) + r = (unsigned char)(r | (*x++ ^ *y++)); + + (*librecrypt_explicit_____)(r); + + return r ? 0 : 1; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + EXPECT(librecrypt_equal_binary("", "", 0u) == 1); + EXPECT(librecrypt_equal_binary("", "", 1u) == 1); + EXPECT(librecrypt_equal_binary("a", "", 1u) == 0); + EXPECT(librecrypt_equal_binary("", "a", 1u) == 0); + EXPECT(librecrypt_equal_binary("a", "a", 1u) == 1); + EXPECT(librecrypt_equal_binary("a", "a", 2u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "abcdef", 6u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "Abcdef", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "AbCdef", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abCdef", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abCdeF", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abcdeF", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "xbcdef", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "xbxdef", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abxdef", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abxdex", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 6u) == 0); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 5u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 4u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 3u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 2u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 1u) == 1); + EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 0u) == 1); + EXPECT(librecrypt_equal_binary(NULL, NULL, 0u) == 1); + return 0; +} + + +#endif diff --git a/librecrypt_fill_with_random_.c b/librecrypt_fill_with_random_.c new file mode 100644 index 0000000..475a156 --- /dev/null +++ b/librecrypt_fill_with_random_.c @@ -0,0 +1,142 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +int +librecrypt_fill_with_random_(void *out, size_t n, ssize_t (*rng)(void *out, size_t n, void *user), void *user) +{ + char *buf = out; + ssize_t r; + + if (!rng) + rng = &librecrypt_rng_; + + while (n) { + r = (*rng)(buf, n, user); + if (r <= 0) { + if (!r) + abort(); + return -1; + } + buf = &buf[(size_t)r]; + n -= (size_t)r; + } + + return 0; +} + + +#else + + +static ssize_t +zero(void *out, size_t n, void *user) +{ + (void) user; + memset(out, 0, n); + return (ssize_t)n; +} + + +static ssize_t +seq(void *out, size_t n, void *user) +{ + unsigned char *restrict buf = out; + unsigned char *restrict num = user; + size_t i; + if (n > 64u) + n = 64u; + for (i = 0u; i < n; i++) + buf[i] = (*num)++; + return (ssize_t)n; +} + + +static ssize_t +next(void *out, size_t n, void *user) +{ + unsigned char *restrict buf = out; + unsigned char *restrict num = user; + assert(n); + *buf = (*num)++; + return 1; +} + + +static ssize_t +failer(void *out, size_t n, void *user) +{ + (void) out; + (void) n; + (void) user; + errno = EDOM; + return -1; +} + + +static ssize_t +zero_ret(void *out, size_t n, void *user) +{ + (void) out; + (void) n; + (void) user; + return 0; +} + + +int +main(void) +{ + unsigned char buf1[1024u]; + unsigned char buf2[sizeof(buf1)]; + unsigned char s; + size_t i; + int rv = 0; + + INIT_TEST_ABORT(); + + SET_UP_ALARM(); + + 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))); + + memset(buf1, 99, sizeof(buf1)); + errno = 0; + EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero, NULL) == 0); + EXPECT(errno == 0); + for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) + EXPECT(!buf1[i]); + + memset(buf1, 99, sizeof(buf1)); + s = 0u; + errno = 0; + EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &seq, &s) == 0); + EXPECT(errno == 0); + for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) + EXPECT(buf1[i] == s); + + memset(buf1, 99, sizeof(buf1)); + s = 0u; + errno = 0; + EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &next, &s) == 0); + EXPECT(errno == 0); + for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) + EXPECT(buf1[i] == s); + + memset(buf1, 99, sizeof(buf1)); + s = 0u; + errno = 0; + EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &failer, NULL) == -1); + EXPECT(errno == EDOM); + for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++) + EXPECT(buf1[i] == 99); + + EXPECT_ABORT(rv = librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero_ret, NULL)); + + return rv; +} + + +#endif diff --git a/librecrypt_find_first_algorithm_.c b/librecrypt_find_first_algorithm_.c new file mode 100644 index 0000000..406f5ac --- /dev/null +++ b/librecrypt_find_first_algorithm_.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +const struct algorithm * +librecrypt_find_first_algorithm_(const char *settings, size_t len) +{ + unsigned r, priority = 0; + const struct algorithm *algo, *found = NULL; + size_t i; + + for (i = 0u;; i++) { + algo = &librecrypt_algorithms_[i]; + if (IS_END_OF_ALGORITHMS(algo)) + break; + r = (*algo->is_algorithm)(settings, len); + if (r > priority) { + priority = r; + found = algo; + } + } + + return found; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_get_encoding.3 b/librecrypt_get_encoding.3 new file mode 100644 index 0000000..e9f7e83 --- /dev/null +++ b/librecrypt_get_encoding.3 @@ -0,0 +1,112 @@ +.TH LIBRECRYPT_GET_ENCODING 3 LIBRECRYPT +.SH NAME +librecrypt_get_encoding - Get encoding alphabet for the last algorithm in a chain + +.SH SYNOPSIS +.nf +#include + +const void *\fBlibrecrypt_get_encoding\fP(const char *\fIsettings\fP, size_t \fIlen\fP, + char *\fIpad_out\fP, int *\fIstrict_pad_out\fP, int \fIdecoding\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_get_encoding () +function returns the ASCII encoding used for +the last algorithm in the algorithm chain +specified by +.IR settings . +.PP +If +.I decoding +is zero, the returned pointer is suitable for +use as the +.I lut +parameter to +.BR librecrypt_encode (3) +and points to an encoding alphabet consisting +of 64 characters repeated 4 times. +.PP +If +.I decoding +is non-zero, the returned pointer is suitable +for use as the +.I lut +parameter to +.BR librecrypt_decode (3) +and points to a reverse lookup table mapping +any valid character (except the padding +character) to its value in the encoding +alphabet and any other character to +.BR 0xFFu . +.PP +On success, +.IR *pad_out +is set to the padding character, or to the +null byte if there is no padding. +.IR *strict_pad_out +is set to 1 if padding is mandatory, +0 otherwise. +.IR *strict_pad_out +is set arbitrarily to 0 or 1 if +.IR *pad_out +is set to the null byte. +.PP +.I pad_out +and +.I strict_pad_out +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_get_encoding () +function returns a pointer to the encoding +data, or +.IR NULL +on failure. On failure, +.IR errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_get_encoding () +function will fail if: +.TP +.B ENOSYS +The last algorithm in +.I settings +is not recognised or was disabled at compile-time. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_get_encoding () +T} Thread safety MT-Safe +T{ +.BR librecrypt_get_encoding () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_get_encoding () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_encode (3), +.BR librecrypt_decode (3) diff --git a/librecrypt_get_encoding.c b/librecrypt_get_encoding.c new file mode 100644 index 0000000..dd5d33d --- /dev/null +++ b/librecrypt_get_encoding.c @@ -0,0 +1,46 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +const void * +librecrypt_get_encoding(const char *settings, size_t len, char *pad_out, int *strict_pad_out, int decoding) +{ + size_t i, start = 0u; + const struct algorithm *algo; + + for (i = 0u; i < len; i++) + if (settings[i] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + start = i + 1u; + settings = &settings[start]; + len -= start; + + algo = librecrypt_find_first_algorithm_(settings, len); + if (!algo) { + errno = ENOSYS; + return NULL; + } + + *pad_out = algo->pad; + *strict_pad_out = algo->strict_pad; + if (decoding) + return algo->decoding_lut; + else + return algo->encoding_lut; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_hash.3 b/librecrypt_hash.3 new file mode 100644 index 0000000..5bf32ea --- /dev/null +++ b/librecrypt_hash.3 @@ -0,0 +1,135 @@ +.TH LIBRECRYPT_HASH 3 LIBRECRYPT +.SH NAME +librecrypt_hash - Compute password hash encoded in ASCII without settings prefix + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_hash\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP, + const char *\fIphrase\fP, size_t \fIlen\fP, + const char *\fIsettings\fP, void *\fIreserved\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_hash () +function computes the hash of a password. +The resulting hash is encoded with +.BR librecrypt_encode (3) +but stored without any settings prefix. +.PP +The password is provided in +.IR phrase +and can contain null bytes; its length is specified by +.IR len . +.PP +The password hash configuration string is provided in +.IR settings . +If +.I settings +contains a resulting hash, it is ignored. +If +.I settings +uses asterisk-encoding to specify random salts, +the function fails. +.PP +The +.I reserved +parameter is reserved for future use and should be +.IR NULL . +.PP +On successful completion, if +.I size +is positive, the output is +null byte-terminated even if truncated. +.PP +Any encountered +.BR EINTR +is ignored. +.PP +On failure, +.I out_buffer +remains unmodified. +.PP +.I settings +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_hash () +function returns the number of bytes that would +have been written to +.IR out_buffer +if +.I size +was sufficiently large, excluding the terminating +null byte. On failure, -1 is returned and +.I errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_hash () +function will fail if: +.TP +.B EINVAL +.I reserved +is not +.IR NULL . +.TP +.B EINVAL +.I settings +is invalid. +.TP +.B EINVAL +.I settings +uses asterisk-encoding to specify random salts. +.TP +.B ERANGE +.I len +is too large or too small for the selected +initial algorithm. +.TP +.B ENOMEM +Failed to allocate internal scratch memory. +.TP +.B ENOSYS +A selected hash algorithm is not recognised or +was disabled at compile-time. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_hash () +T} Thread safety MT-Safe +T{ +.BR librecrypt_hash () +T} Async-signal safety AS-Unsafe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_hash () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_hash_binary (3), +.BR librecrypt_crypt (3), +.BR librecrypt_test_supported (3) diff --git a/librecrypt_hash.c b/librecrypt_hash.c new file mode 100644 index 0000000..267aa31 --- /dev/null +++ b/librecrypt_hash.c @@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved) +{ + return librecrypt_hash_(out_buffer, size, phrase, len, settings, reserved, ASCII_HASH); +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_hash_.c b/librecrypt_hash_.c new file mode 100644 index 0000000..ed842e9 --- /dev/null +++ b/librecrypt_hash_.c @@ -0,0 +1,187 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +static ssize_t +zero_generator(void *out, size_t n, void *user) +{ + (void) user; + if (n > (size_t)SSIZE_MAX) + n = (size_t)SSIZE_MAX; + memset(out, 0, n); + return (ssize_t)n; +} + + +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) +{ + const struct algorithm *algo; + ssize_t (*rng)(void *out, size_t n, void *user) = NULL; + 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; + int has_next, phrase_scratch_i = 0; + ssize_t r_len; + int r; + void *new; + + if (reserved != NULL) { + errno = EINVAL; + return -1; + } + + if (!size) + rng = &zero_generator; + + if (strchr(settings, '*')) { + if (action != ASCII_CRYPT) { + errno = EINVAL; + return -1; + } + r_len = librecrypt_realise_salts(out_buffer, size, settings, rng, NULL); + if (r_len < 0) { + if (errno == ERANGE) + errno = ENOMEM; + return -1; + } else if ((size_t)r_len >= size) { + settings_scratch = malloc((size_t)r_len + 1u); + if (!settings_scratch) + return -1; + if (librecrypt_realise_salts(settings_scratch, (size_t)r_len + 1u, settings, rng, NULL) != r_len) + abort(); + settings = settings_scratch; + } + } + +next: + has_next = 0; + for (n = 0u; settings[n]; n++) { + if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { + has_next = 1; + break; + } + } + + 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; + } + + if (action == ASCII_CRYPT) { + min = size ? size - 1u < prefix ? size - 1u : prefix : 0u; + size -= min; + memcpy(out_buffer, settings, min); + out_buffer = &out_buffer[min]; + ret += prefix; + } + + if (size && phrase_scratch_sizes[phrase_scratch_i] < algo->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); + if (!new) { + free(phrase_scratches[phrase_scratch_i]); + phrase_scratches[phrase_scratch_i] = NULL; + phrase_scratch_sizes[phrase_scratch_i] = 0u; + goto fail; + } + phrase_scratches[phrase_scratch_i] = new; + phrase_scratch_sizes[phrase_scratch_i] = algo->hash_size; + } + + if (has_next) { + 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); + } else if (action == BINARY_HASH) { + hash_to_output: + r = (*algo->hash)(out_buffer, size, phrase, len, settings, prefix, reserved); + } else if (size < algo->hash_size) { + goto hash_to_scratch; + } else { + goto hash_to_output; + } + if (r < 0) + goto fail; + + if (!has_next) { + if (action == BINARY_HASH) { + ret += algo->hash_size; + } else if (!size) { + ascii_len = algo->hash_size % 3u; + if (ascii_len) { + if (algo->pad && algo->strict_pad) + ascii_len = 4u; + else + ascii_len += 1u; + } + ascii_len += algo->hash_size / 3u * 4u; + goto include_ascii; + } else { + 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'); + include_ascii: + min = size ? size - 1u < ascii_len ? size - 1u : ascii_len : 0u; + out_buffer = &out_buffer[min]; + size -= min; + ret += ascii_len; + } + } else { + phrase = size ? phrase_scratches[phrase_scratch_i] : NULL; + phrase_scratch_i ^= 1; + len = algo->hash_size; + + settings = &settings[n]; + if (action == ASCII_CRYPT) { + ret += 1u; + if (size) { + *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER; + size -= 1u; + } + } + settings++; + goto next; + } + + librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]); + librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]); + librecrypt_wipe_str(settings_scratch); + free(phrase_scratches[0u]); + free(phrase_scratches[1u]); + free(settings_scratch); + + if (size && action != BINARY_HASH) + out_buffer[0] = '\0'; + return (ssize_t)ret; + +fail: + librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]); + librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]); + librecrypt_wipe_str(settings_scratch); + free(phrase_scratches[0u]); + free(phrase_scratches[1u]); + free(settings_scratch); + return -1; +} + + +#else + + +/* Tested via librecrypt_hash_binary, librecrypt_hash, and librecrypt_crypt */ +CONST int main(void) { return 0; } + + +#endif diff --git a/librecrypt_hash_binary.3 b/librecrypt_hash_binary.3 new file mode 100644 index 0000000..720b82d --- /dev/null +++ b/librecrypt_hash_binary.3 @@ -0,0 +1,138 @@ +.TH LIBRECRYPT_HASH_BINARY 3 LIBRECRYPT +.SH NAME +librecrypt_hash_binary - Compute password hash in raw binary form + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_hash_binary\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP, + const char *\fIphrase\fP, size_t \fIlen\fP, + const char *\fIsettings\fP, void *\fIreserved\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_hash_binary () +function computes the hash of a password. +The hash is stored in raw binary format without any +settings prefix. +.PP +The password is provided in +.IR phrase +and can contain null bytes; its length is specified by +.IR len . +.PP +The password hash configuration string is provided in +.IR settings . +If +.I settings +contains a resulting hash, it is ignored. +If +.I settings +uses asterisk-encoding to specify random salts, +the function fails. +.PP +The +.I reserved +parameter is reserved for future use and should be +.IR NULL . +.PP +On successful completion, up to +.I size +bytes are written to +.IR out_buffer ; +the return value describes how many bytes would have +been written if +.I size +was sufficiently large. +.PP +Any encountered +.BR EINTR +is ignored. +.PP +On failure, +.I out_buffer +remains unmodified. +.PP +.I settings +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_hash_binary () +function returns the number of bytes that would +have been written to +.IR out_buffer +if +.I size +was sufficiently large. +On failure, -1 is returned and +.I errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_hash_binary () +function will fail if: +.TP +.B EINVAL +.I reserved +is not +.IR NULL . +.TP +.B EINVAL +.I settings +is invalid. +.TP +.B EINVAL +.I settings +uses asterisk-encoding to specify random salts. +.TP +.B ERANGE +.I len +is too large or too small for the selected +initial algorithm. +.TP +.B ENOMEM +Failed to allocate internal scratch memory. +.TP +.B ENOSYS +A selected hash algorithm is not recognised or +was disabled at compile-time. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_hash_binary () +T} Thread safety MT-Safe +T{ +.BR librecrypt_hash_binary () +T} Async-signal safety AS-Unsafe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_hash_binary () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_hash (3), +.BR librecrypt_crypt (3), +.BR librecrypt_test_supported (3) diff --git a/librecrypt_hash_binary.c b/librecrypt_hash_binary.c new file mode 100644 index 0000000..c119c78 --- /dev/null +++ b/librecrypt_hash_binary.c @@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_hash_binary(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved) +{ + return librecrypt_hash_(out_buffer, size, phrase, len, settings, reserved, BINARY_HASH); +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_make_settings.3 b/librecrypt_make_settings.3 new file mode 100644 index 0000000..f742df6 --- /dev/null +++ b/librecrypt_make_settings.3 @@ -0,0 +1,177 @@ +.TH LIBRECRYPT_MAKE_SETTINGS 3 LIBRECRYPT +.SH NAME +librecrypt_make_settings - Generate a password hash settings string + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_make_settings\fP(char *\fIout_buffer\fP, size_t \fIsize\fP, const char *\fIalgorithm\fP, + size_t \fImemcost\fP, uintmax_t \fItimecost\fP, int \fIgensalt\fP, + ssize_t (*\fIrng\fP)(void *\fIout\fP, size_t \fIn\fP, void *\fIuser\fP), void *\fIuser\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_make_settings () +function generates a password hash setting string +and writes it to +.IR out_buffer . +.PP +If +.I algorithm +is +.IR NULL , +a default strong algorithm is selected. +The empty string selects the deprecated DES algorithm. +Algorithm chains are disallowed for this function. +.PP +The +.I memcost +parameter specifies an approximate memory cost in bytes. +A default value is selected if 0 is specified. +.PP +The +.I timecost +parameter specifies a total time cost in an +implementation-dependent, monotonic unit. +A default value is selected if 0 is specified. +.PP +If +.I gensalt +is non-zero, random salts are included in the +generated output, otherwise the output will only +specify the sizes the salts shall have. +.PP +If +.I gensalt +is non-zero, +.IR *rng , +or a default implementation (which tries to use +cryptographic random number generation but has a +non-cryptographic random number generation as a +last resort) if +.I rng +is +.IR NULL , +used to generate random bytes. +.I *rng +shall fill +.I out +with at least 1, and up to +.IR n , +random bytes, and return the number of bytes +written, or return -1 on failure. +.PP +The +.I data +argument is passed into the +.IR *rng -parameter +with the same name, as is and may be used by +.IR *rng +for user-defined purposes. +.PP +On successful completion, if +.I size +is positive, the output is null byte terminated +even if truncated. +.PP +If +.I rng +is +.IR NULL , +any encountered +.BR EINTR +is ignored (cannot happen if +.I gensalt +is zero); if it is encountered, +.I errno +will be set to +.BR EINTR , +but is otherwise left unmodified unless the function +fails due to one of the errors listed below. + +.SH RETURN VALUES +The +.BR librecrypt_make_settings () +function returns the number of bytes that would have +been written to +.IR out_buffer +if +.I size +was sufficiently large, or -1 on failure and +.I errno +is set to describe the error. + +.SH ERRORS +The +.BR librecrypt_make_settings () +function will fail if: +.TP +.B EINVAL +.I algorithm +represents a chain of algorithms. +.TP +.B ENOSYS +.I algorithm +represents an algorithm that is not recognised or +was disabled at compile-time. +.TP +.B ENOSYS +.I algorithm +is +.IR NULL +but all algorithms were disabled at compile-time. +.PP +Additionally, unless +.I rng +is +.IR NULL , +.I *rng +may set +.I errno +(if +.I gensalt +is non-zero). + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_make_settings () +T} Thread safety MT-Safe +T{ +.BR librecrypt_make_settings () +T} Async-signal safety AS-Unsafe +.TE +.sp +.PP +If +.I gensalt +is non-zero and +.I rng +is +.RI non- NULL , +.I *rng +may weaken the safety attributes. + +.SH HISTORY +The +.BR librecrypt_make_settings () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_realise_salts (3) diff --git a/librecrypt_make_settings.c b/librecrypt_make_settings.c new file mode 100644 index 0000000..99c7e3b --- /dev/null +++ b/librecrypt_make_settings.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_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) +{ + const struct algorithm *algo; + + if (!algorithm) { + algo = &librecrypt_algorithms_[0]; + if (IS_END_OF_ALGORITHMS(algo)) + goto enosys; + } else { + if (strchr(algorithm, LIBRECRYPT_ALGORITHM_LINK_DELIMITER)) { + errno = EINVAL; + return -1; + } + algo = librecrypt_find_first_algorithm_(algorithm, strlen(algorithm)); + if (!algo) + goto enosys; + } + + if (!rng) + rng = &librecrypt_rng_; + + return (*algo->make_settings)(out_buffer, size, algorithm, memcost, timecost, gensalt, rng, user); + +enosys: + errno = ENOSYS; + return -1; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_next_algorithm.3 b/librecrypt_next_algorithm.3 new file mode 100644 index 0000000..ca8bdf3 --- /dev/null +++ b/librecrypt_next_algorithm.3 @@ -0,0 +1,96 @@ +.TH LIBRECRYPT_NEXT_ALGORITHM 3 LIBRECRYPT +.SH NAME +librecrypt_next_algorithm - Iterate over algorithms in a chained password hash string + +.SH SYNOPSIS +.nf +#include +.PP +char *\fBlibrecrypt_next_algorithm\fP(char **\fIhash\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_next_algorithm () +function is called repeatedly to extract each hash +algorithm (including its parameters) that shall be +chained together according to a provided hash string. +.PP +On the initial call, +.I *hash +shall point to the password hash string. +On each call the function updates +.I *hash +to its current parsing state. On each call except +the final one, the string is modified by replacing +the next instance of +.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER +(which is +.BR \(aq>\(aq ) +with a null byte. +.PP +Each call returns the next algorithm substring. +Once all algorithms have been extracted and returned, +.IR NULL +is returned. +.PP +Except once the function has returned +.IR NULL , +overwriting the terminating null byte in the previously +returned substring +with +.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER +restores the original hash string. +.PP +Unless already stripped out before input, the last +returned substring ends with the resulting hash. +.PP +.I hash +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_next_algorithm () +function returns a pointer to the next algorithm +substring, or +.IR NULL +when no more algorithms remain. + +.SH ERRORS +The +.BR librecrypt_next_algorithm () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_next_algorithm () +T} Thread safety MT-Safe +T{ +.BR librecrypt_next_algorithm () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_next_algorithm () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_chain_length (3), +.BR librecrypt_decompose_chain (3), +.BR librecrypt_decompose_chain1 (3) diff --git a/librecrypt_next_algorithm.c b/librecrypt_next_algorithm.c new file mode 100644 index 0000000..b825582 --- /dev/null +++ b/librecrypt_next_algorithm.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline char *librecrypt_next_algorithm(char **hash); + + +#else + + +static void +testcase_1(void) +{ + char hash[] = ">a$b>c$d>e$f$"; + char *s = hash, *a; + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s != NULL); + EXPECT(!strcmp(a, "")); + EXPECT(!strcmp(s, "a$b>c$d>e$f$")); + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s != NULL); + EXPECT(!strcmp(a, "a$b")); + EXPECT(!strcmp(s, "c$d>e$f$")); + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s != NULL); + EXPECT(!strcmp(a, "c$d")); + EXPECT(!strcmp(s, "e$f$")); + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s == NULL); + EXPECT(!strcmp(a, "e$f$")); + + EXPECT(librecrypt_next_algorithm(&s) == NULL); + EXPECT(s == NULL); +} + + +static void +testcase_2(void) +{ + char hash[] = "a$b>c$d>e$f$>"; + char *s = hash, *a; + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s != NULL); + EXPECT(!strcmp(a, "a$b")); + EXPECT(!strcmp(s, "c$d>e$f$>")); + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s != NULL); + EXPECT(!strcmp(a, "c$d")); + EXPECT(!strcmp(s, "e$f$>")); + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s != NULL); + EXPECT(!strcmp(a, "e$f$")); + EXPECT(!strcmp(s, "")); + + EXPECT((a = librecrypt_next_algorithm(&s))); + EXPECT(s == NULL); + EXPECT(!strcmp(a, "")); + + EXPECT(librecrypt_next_algorithm(&s) == NULL); + EXPECT(s == NULL); +} + + +int +main(void) +{ + SET_UP_ALARM(); + testcase_1(); + testcase_2(); + return 0; +} + + +#endif diff --git a/librecrypt_realise_salts.3 b/librecrypt_realise_salts.3 new file mode 100644 index 0000000..8a743cc --- /dev/null +++ b/librecrypt_realise_salts.3 @@ -0,0 +1,161 @@ +.TH LIBRECRYPT_REALISE_SALTS 3 LIBRECRYPT +.SH NAME +librecrypt_realise_salts - Realise asterisk-encoded random salts in a settings string + +.SH SYNOPSIS +.nf +#include + +ssize_t \fBlibrecrypt_realise_salts\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP, const char *\fIsettings\fP, + ssize_t (*\fIrng\fP)(void *\fIout\fP, size_t \fIn\fP, void *\fIuser\fP), void *\fIuser\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_realise_salts () +function locates all asterisks followed by a +non-negative decimal number in +.IR settings +and replaces each with ASCII-encoded random +bytes (as many bytes as the number specifies), +writing the result to +.IR out_buffer . +.PP +.IR *rng , +or a default implementation (which tries to use +cryptographic random number generation but has +a non-cryptographic random number generation as +a last resort) if +.I rng +is +.IR NULL , +used to generate random bytes. +.I *rng +shall fill out with at least 1, and up to +.IR n , +random bytes, and return the number of bytes +written, or return -1 on failure. +.PP +The +.I data +argument is passed into the +.IR *rng -parameter +with the same name, as is and may be used by +.I *rng +for user-defined purposes. +.PP +If +.I rng +is +.IR NULL , +a default cryptographic random number generator +is used. The +.I *rng +function shall generate between 1 and +.I n +bytes (inclusively), write them to the front of +.IR out , +and return the number of generated bytes, or +-1 on failure. +.PP +On successful completion, if +.I size +is positive, the output is +null byte-terminated even if truncated. +.PP +If +.I rng +is +.IR NULL , +any encountered +.BR EINTR +is ignored; if it is encountered, +.I errno +will be set to +.BR EINTR , +but is otherwise left unmodified unless the +function fails due to one of the +errors listed in the +.B ERRORS +section. +.PP +.I settings +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_realise_salts () +function returns the number of bytes that +would have been written to +.IR out_buffer +if +.I size +was sufficiently large, or -1 on failure. + +.SH ERRORS +The +.BR librecrypt_realise_salts () +function will fail if: +.TP +.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 +disabled at compile-time. +.PP +Additionally, unless +.I rng +is +.IR NULL , +.I *rng +may set +.IR errno . + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_realise_salts () +T} Thread safety MT-Safe +T{ +.BR librecrypt_realise_salts () +T} Async-signal safety T{ +AS-Safe /\fIrng\fP +.br +AS-Unsafe /\fI!rng\fP +T} +.TE +.sp +.PP +.I rng +is +.RI non- NULL , +.I *rng +may weaken the safety attributes. + +.SH HISTORY +The +.BR librecrypt_realise_salts () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_make_settings (3) diff --git a/librecrypt_realise_salts.c b/librecrypt_realise_salts.c new file mode 100644 index 0000000..a4204a6 --- /dev/null +++ b/librecrypt_realise_salts.c @@ -0,0 +1,151 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *settings, + ssize_t (*rng)(void *out, size_t n, void *user), void *user) +{ + const char *lut; + char pad; + int strict_pad, has_asterisk, nul_term = 0; + size_t i, j, n, min, ret = 0u; + size_t count, digit, q, r, left, mid, right; + + 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; + } + + lut = librecrypt_get_encoding(settings, n, &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; + } + + if (++i == n) { + if (size) { + *out_buffer++ = '*'; + size -= 1u; + } + ret += 1u; + break; + } + if ('0' > settings[i] || settings[i] > '9') { + if (size) { + *out_buffer++ = '*'; + size -= 1u; + } + ret += 1u; + continue; + } + + count = 0; + for (; i < n && '0' <= settings[i] && settings[i] <= '9'; i++) { + digit = (size_t)(settings[i] - '0'); + if (count > ((size_t)SSIZE_MAX - digit) / 10u) + goto erange; + count *= 10u; + count += digit; + } + + q = count / 3u; + r = count % 3u; + mid = r ? r + 1u : 0u; + right = (!r ? 0u : pad ? 4u : r + 1u); + if (q > ((size_t)SSIZE_MAX - right) / 4u) + goto erange; + left = q * 4u; + ret += left + mid + right; + + left += mid; + if (left > size) { + left = size; + mid = 0; + } + 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)]; + out_buffer = &out_buffer[left]; + size -= left; + + if (right > size) + right = size; + for (j = 0; j < right; j++) + out_buffer[right] = pad; + out_buffer = &out_buffer[right]; + size -= right; + } + } + + if (nul_term) + *out_buffer = '\0'; + if (ret > (size_t)SSIZE_MAX) + goto erange; + return (ssize_t)ret; + +erange: + errno = ERANGE; + return -1; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_rng_.c b/librecrypt_rng_.c new file mode 100644 index 0000000..8b9f138 --- /dev/null +++ b/librecrypt_rng_.c @@ -0,0 +1,146 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +ssize_t +librecrypt_rng_(void *out, size_t n, void *user) +{ + static unsigned int seed = 0u; + unsigned char *buf = out; + int v, fd, saved_errno = errno; + unsigned int state; + size_t i, min, ret = 0u; + ssize_t r; + struct timespec ts; +#if defined(__linux__) && defined(AT_RANDOM) + const unsigned char *at_random; + uintptr_t at_random_addr; +#endif + + (void) user; + + if (!n) + return 0; + + if (n > (size_t)SSIZE_MAX) + n = (size_t)SSIZE_MAX; + +#if defined(__linux__) + for (;;) { + r = getrandom(buf, n, 0u); + if (r < 0) { + if (errno != EINTR) + break; + saved_errno = EINTR; + } else if (r > 0) { + ret += (size_t)r; + buf = &buf[(size_t)r]; + n -= (size_t)r; + if (!n) + goto out; + } else { + break; + } + } +#endif + + for (;;) { + min = n < 256u ? n : 256u; + if (getentropy(buf, min)) { + if (errno != EINTR) + break; + saved_errno = EINTR; + } else { + ret += min; + buf = &buf[min]; + n -= min; + if (!n) + goto out; + } + } + +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + for (;;) { + r = read(fd, buf, n); + if (r < 0) { + if (errno != EINTR) { + close(fd); + break; + } + saved_errno = EINTR; + } else if (r > 0) { + ret += (size_t)r; + buf = &buf[(size_t)r]; + n -= (size_t)r; + if (!n) { + close(fd); + goto out; + } + } else { + close(fd); + break; + } + } + } + + state = seed; + if (!state) { + 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; + for (i = 0u; i < 16u; i++) + state ^= (unsigned int)at_random[i] << (i % sizeof(unsigned int) * 8u); + } + } +#endif + 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; + seed = state; + +out: + errno = saved_errno; + return (ssize_t)ret; +} + + +#else + + +int +main(void) +{ + /* TODO test failure cases */ + + unsigned char buf1[1024u]; + unsigned char buf2[sizeof(buf1)]; + ssize_t n1, n2; + + SET_UP_ALARM(); + + n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); + n2 = librecrypt_rng_(buf2, sizeof(buf2), NULL); + 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))); + + return 0; +} + + +#endif diff --git a/librecrypt_settings_prefix.3 b/librecrypt_settings_prefix.3 new file mode 100644 index 0000000..622af0e --- /dev/null +++ b/librecrypt_settings_prefix.3 @@ -0,0 +1,80 @@ +.TH LIBRECRYPT_SETTINGS_PREFIX 3 LIBRECRYPT +.SH NAME +librecrypt_settings_prefix - Get length of settings prefix in a password hash string + +.SH SYNOPSIS +.nf +#include + +size_t \fBlibrecrypt_settings_prefix\fP(const char *\fIhash\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_settings_prefix () +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. +.PP +.I hash +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_settings_prefix () +function returns the number of bytes that make up the +settings prefix. The return value may be 0, which shall +be treated as any other valid return value. +.PP +.IR &hash[r] , +where +.I r +is the return value, points to the hash result proper. + +.SH ERRORS +The +.BR librecrypt_settings_prefix () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_settings_prefix () +T} Thread safety MT-Safe +T{ +.BR librecrypt_settings_prefix () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_settings_prefix () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_chain_length (3) diff --git a/librecrypt_settings_prefix.c b/librecrypt_settings_prefix.c new file mode 100644 index 0000000..edc7222 --- /dev/null +++ b/librecrypt_settings_prefix.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline size_t librecrypt_settings_prefix(const char *hash); + + +#else + + +#define CHECK(PREFIX, SUFFIX)\ + do {\ + EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX) == sizeof(PREFIX) - 1u);\ + EXPECT(librecrypt_settings_prefix(PREFIX) == sizeof(PREFIX) - 1u);\ + } while (0) + + +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"); + return 0; +} + + +#endif diff --git a/librecrypt_test_supported.3 b/librecrypt_test_supported.3 new file mode 100644 index 0000000..eb51e2d --- /dev/null +++ b/librecrypt_test_supported.3 @@ -0,0 +1,98 @@ +.TH LIBRECRYPT_TEST_SUPPORTED 3 LIBRECRYPT +.SH NAME +librecrypt_test_supported - Check whether an algorithm chain is supported + +.SH SYNOPSIS +.nf +#include + +int \fBlibrecrypt_test_supported\fP(const char *\fIphrase\fP, size_t \fIlen\fP, int \fItext\fP, const char *\fIsettings\fP); +.fi +.PP +Link with +.IR -lrecrypt . +Static linking may require additional flags +depending on enabled hash algorithms. + +.SH DESCRIPTION +The +.BR librecrypt_test_supported () +function checks whether a hash algorithm chain is +supported for the given input, and that each +algorithm configuration is valid. +.PP +The +.I settings +argument is a password hash string. Algorithm +tuning parameters and the hash result may be omitted. +.PP +The +.I phrase +argument is the password to hash. +It may contain null bytes and may be +.IR NULL +even if +.I len +is non-zero. If +.I phrase +is +.IR NULL , +the function checks whether the specified length +is supported and assumes an arbitrary byte +sequence if +.I text +is zero. If +.I text +is non-zero, UTF-8 without null bytes is assumed. +.PP +If +.I phrase +is not +.IR NULL , +.I text +is ignored. +.PP +.I settings +must not be +.IR NULL . + +.SH RETURN VALUES +The +.BR librecrypt_test_supported () +function returns 1 if the configuration is supported, +and 0 otherwise. + +.SH ERRORS +The +.BR librecrypt_test_supported () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_test_supported () +T} Thread safety MT-Safe +T{ +.BR librecrypt_test_supported () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_test_supported () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_hash_binary (3), +.BR librecrypt_hash (3), +.BR librecrypt_crypt (3) diff --git a/librecrypt_test_supported.c b/librecrypt_test_supported.c new file mode 100644 index 0000000..fad8bd9 --- /dev/null +++ b/librecrypt_test_supported.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +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; + + for (;;) { + has_next = 0; + for (n = 0u; settings[n]; n++) { + if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { + has_next = 1; + break; + } + } + + algo = librecrypt_find_first_algorithm_(settings, n); + if (!algo) + return 0; + + prefix = (*algo->get_prefix)(settings, n); + if (has_next && prefix < n) + return 0; + + if (!(*algo->test_supported)(phrase, len, text, settings, prefix)) + return 0; + + if (!has_next) + return 1; + + phrase = NULL; + len = algo->hash_size; + text = 0; + + settings = &settings[n]; + settings++; + } +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + return 0; +} + + +#endif +/* TODO test */ diff --git a/librecrypt_wipe.3 b/librecrypt_wipe.3 new file mode 100644 index 0000000..48f5829 --- /dev/null +++ b/librecrypt_wipe.3 @@ -0,0 +1,57 @@ +.TH LIBRECRYPT_WIPE 3 LIBRECRYPT +.SH NAME +librecrypt_wipe - Securely erase a memory buffer + +.SH SYNOPSIS +.nf +#include + +void \fBlibrecrypt_wipe\fP(void *\fIbuffer\fP, size_t \fIsize\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_wipe () +function securely erases +.I size +bytes from +.IR buffer . + +.SH RETURN VALUES +None. + +.SH ERRORS +The +.BR librecrypt_wipe () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_wipe () +T} Thread safety MT-Safe +T{ +.BR librecrypt_wipe () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_wipe () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_wipe_str (3) diff --git a/librecrypt_wipe.c b/librecrypt_wipe.c new file mode 100644 index 0000000..a704ef0 --- /dev/null +++ b/librecrypt_wipe.c @@ -0,0 +1,65 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wsuggest-attribute=const" +# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#endif + + +void *(*volatile librecrypt_explicit_memset_____)(void *, int, size_t) = &memset; + + +#if defined(__clang__) /* before __GNUC__ because that is also set in clang */ +# if __has_attribute(optnone) +__attribute__((optnone)) +# endif +#elif defined(__GNUC__) +# if __GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ >= 40400 +__attribute__((optimize("-O0"))) +# endif +#endif +void +librecrypt_wipe(void *buffer, size_t size) +{ + if (buffer && size) { + buffer = (*librecrypt_explicit_memset_____)(buffer, 0, size); +#if defined(__GNUC__) + __asm__ volatile ("" :: "g" (buffer) : "memory"); +#else + (void) buffer; +#endif + } +} + + +#else + + +int +main(void) +{ + char *buf; + + SET_UP_ALARM(); + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wnonnull" +#endif + + librecrypt_wipe(NULL, 0u); + librecrypt_wipe(NULL, 4094u); + librecrypt_wipe((void *)(intptr_t)-1, 0u); + + buf = malloc(256u); + memset(buf, 99, 256u); + librecrypt_wipe(buf, 256u); + free(buf); /* TODO should test memory is wiped */ + + return 0; +} + + +#endif diff --git a/librecrypt_wipe_str.3 b/librecrypt_wipe_str.3 new file mode 100644 index 0000000..80aa301 --- /dev/null +++ b/librecrypt_wipe_str.3 @@ -0,0 +1,61 @@ +.TH LIBRECRYPT_WIPE_STR 3 LIBRECRYPT +.SH NAME +librecrypt_wipe_str - Securely erase a string + +.SH SYNOPSIS +.nf +#include + +void \fBlibrecrypt_wipe_str\fP(char *\fIstring\fP); +.fi +.PP +Link with +.IR -lrecrypt . + +.SH DESCRIPTION +The +.BR librecrypt_wipe_str () +function securely erases the string referenced by +.IR string . +.PP +If +.I string +is +.IR NULL , +no action is taken. + +.SH RETURN VALUES +None. + +.SH ERRORS +The +.BR librecrypt_wipe_str () +function cannot fail. + +.SH ATTRIBUTES +For an explanation of the terms used in this section, see +.BR attributes (7). +.PP +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.BR librecrypt_wipe_str () +T} Thread safety MT-Safe +T{ +.BR librecrypt_wipe_str () +T} Async-signal safety AS-Safe +.TE +.sp + +.SH HISTORY +The +.BR librecrypt_wipe_str () +function was introduced in version 1.0 of +.BR librecrypt . + +.SH SEE ALSO +.BR librecrypt (7), +.BR librecrypt_wipe (3) diff --git a/librecrypt_wipe_str.c b/librecrypt_wipe_str.c new file mode 100644 index 0000000..8c31cdc --- /dev/null +++ b/librecrypt_wipe_str.c @@ -0,0 +1,39 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline void librecrypt_wipe_str(char *string); + + +#else + + +#define CHECK(TEXT)\ + do {\ + memset(buf, 0, sizeof(buf));\ + stpcpy(buf, (TEXT));\ + assert(!strcmp(buf, (TEXT)));\ + librecrypt_wipe_str(buf);\ + for (i = 0u; i < sizeof(buf); i++)\ + EXPECT(!buf[i]);\ + } while (0) + + +int +main(void) +{ + char buf[64u]; + size_t i; + SET_UP_ALARM(); + librecrypt_wipe_str(NULL); + CHECK(""); + CHECK("hello"); + CHECK("hello developer"); + CHECK(" hello developer "); + CHECK("\1 hello developer \1"); + return 0; +} + + +#endif diff --git a/mk/linux.mk b/mk/linux.mk new file mode 100644 index 0000000..ad58f69 --- /dev/null +++ b/mk/linux.mk @@ -0,0 +1,6 @@ +LIBEXT = so +LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR) +LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR) +LIBMINOREXT = $(LIBEXT).$(LIB_VERSION) + +FIX_INSTALL_NAME = : diff --git a/mk/macos.mk b/mk/macos.mk new file mode 100644 index 0000000..1b172e6 --- /dev/null +++ b/mk/macos.mk @@ -0,0 +1,6 @@ +LIBEXT = dylib +LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION) +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) + +FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/librecrypt.$(LIBMAJOREXT)" diff --git a/mk/windows.mk b/mk/windows.mk new file mode 100644 index 0000000..ed5ec8d --- /dev/null +++ b/mk/windows.mk @@ -0,0 +1,6 @@ +LIBEXT = dll +LIBFLAGS = -shared +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) + +FIX_INSTALL_NAME = : -- cgit v1.2.3-70-g09d2