aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2022-01-16 19:58:57 +0100
committerMattias Andrée <maandree@kth.se>2022-01-16 19:58:57 +0100
commit8bdc2e4929068210d523c34c0b171b51ce96057f (patch)
treec77331e77b9777a37716bffb7365c4486a6df9a0
downloadlibar2-1.0.tar.gz
libar2-1.0.tar.bz2
libar2-1.0.tar.xz
First commit1.0
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to '')
-rw-r--r--.gitignore15
-rw-r--r--LICENSE15
-rw-r--r--Makefile88
-rw-r--r--common.h64
-rw-r--r--config.mk8
-rw-r--r--libar2.h618
-rw-r--r--libar2_decode_base64.c68
-rw-r--r--libar2_decode_params.c147
-rw-r--r--libar2_earse.c38
-rw-r--r--libar2_encode_base64.c42
-rw-r--r--libar2_encode_params.c31
-rw-r--r--libar2_hash.c612
-rw-r--r--libar2_latest_argon2_version.c5
-rw-r--r--libar2_string_to_type.c30
-rw-r--r--libar2_string_to_version.c20
-rw-r--r--libar2_type_to_string.c19
-rw-r--r--libar2_validate_params.c20
-rw-r--r--libar2_version_to_string.c18
-rw-r--r--libar2_version_to_string_proper.c18
-rw-r--r--mk/linux.mk4
-rw-r--r--mk/macos.mk4
-rw-r--r--mk/windows.mk4
-rw-r--r--test.c736
23 files changed, 2624 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2c489c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/test
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d0aa103
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2022 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ea08bac
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,88 @@
+.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 = ar2
+
+
+OBJ =\
+ libar2_decode_base64.o\
+ libar2_decode_params.o\
+ libar2_encode_base64.o\
+ libar2_encode_params.o\
+ libar2_earse.o\
+ libar2_hash.o\
+ libar2_latest_argon2_version.o\
+ libar2_string_to_type.o\
+ libar2_string_to_version.o\
+ libar2_type_to_string.o\
+ libar2_validate_params.o\
+ libar2_version_to_string.o\
+ libar2_version_to_string_proper.o
+
+HDR =\
+ libar2.h\
+ common.h
+
+LOBJ = $(OBJ:.o=.lo)
+
+
+all: libar2.a libar2.$(LIBEXT) test
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+test.o: test.c $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+test: test.o libar2.a
+ $(CC) -o $@ test.o libar2.a $(LDFLAGS)
+
+libar2.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+
+libar2.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: test
+ ./test
+
+install: libar2.a libar2.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- libar2.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libar2.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libar2.$(LIBMINOREXT)"
+ ln -sf -- libar2.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libar2.$(LIBMAJOREXT)"
+ ln -sf -- libar2.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libar2.$(LIBEXT)"
+ cp -- libar2.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/libar2.h"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) test
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c
+
+.PHONY: all check install uninstall clean
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..dd6f6c1
--- /dev/null
+++ b/common.h
@@ -0,0 +1,64 @@
+/* See LICENSE file for copyright and license details. */
+#include "libar2.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#include <libblake.h>
+
+
+#ifndef UINT_LEAST32_C
+# ifdef UINT32_C
+# define UINT_LEAST32_C(V) UINT32_C(V)
+# else
+# define UINT_LEAST32_C(V) V##UL
+# endif
+#endif
+
+#ifndef UINT_LEAST64_C
+# ifdef UINT64_C
+# define UINT_LEAST64_C(V) UINT64_C(V)
+# else
+# define UINT_LEAST64_C(V) V##ULL
+# endif
+#endif
+
+
+#ifndef CACHE_LINE_SIZE
+# define CACHE_LINE_SIZE 256 /* better with larger than actual than smaller than actual */
+#endif
+
+
+#ifndef ALIGNOF
+# ifdef __STDC_VERSION__
+# if __STDC_VERSION__ >= 201112L
+# define ALIGNOF(X) _Alignof(X)
+# endif
+# endif
+#endif
+#ifndef ALIGNOF
+# ifdef __GNUC__
+# define ALIGNOF(X) __alignof__(X)
+# else
+# define ALIGNOF(X) sizeof(X)
+# endif
+#endif
+
+#define ELEMSOF(ARR) (sizeof(ARR) / sizeof(*(ARR)))
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+
+
+#define ERASE(PTR, N) libar2_earse(PTR, N)
+#define ERASE_ARRAY(ARR) ERASE(ARR, sizeof(ARR))
+#define ERASE_STRUCT(S) ERASE(&(S), sizeof(S))
+
+
+struct block {
+ uint_least64_t w[1024 / (64 / 8)];
+};
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..3a09d91
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c11 -Wall -O3
+LDFLAGS = -s
diff --git a/libar2.h b/libar2.h
new file mode 100644
index 0000000..07e60eb
--- /dev/null
+++ b/libar2.h
@@ -0,0 +1,618 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBAR2_H
+#define LIBAR2_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* for internal use { */
+
+#if defined(UINT_LEAST32_C)
+# define LIBAR2_UINT_LEAST32_C__(V) UINT_LEAST32_C(V)
+#elif defined(UINT32_C)
+# define LIBAR2_UINT_LEAST32_C__(V) UINT32_C(V)
+#else
+# define LIBAR2_UINT_LEAST32_C__(V) V##UL
+#endif
+
+#define LIBAR2_MIN_T_COST LIBAR2_UINT_LEAST32_C__(1)
+#define LIBAR2_MAX_T_COST LIBAR2_UINT_LEAST32_C__(0xFFFFffff)
+#define LIBAR2_MIN_M_COST LIBAR2_UINT_LEAST32_C__(8)
+#define LIBAR2_MAX_M_COST ((uint_least32_t)((SIZE_MAX >> 11) & LIBAR2_UINT_LEAST32_C__(0xFFFFffff)))
+#define LIBAR2_MIN_LANES LIBAR2_UINT_LEAST32_C__(1)
+#define LIBAR2_MAX_LANES LIBAR2_UINT_LEAST32_C__(0xFFFFff)
+#define LIBAR2_MIN_SALTLEN ((size_t)LIBAR2_UINT_LEAST32_C__(8))
+#define LIBAR2_MAX_SALTLEN ((size_t)LIBAR2_UINT_LEAST32_C__(0xFFFFffff))
+#define LIBAR2_MAX_KEYLEN ((size_t)LIBAR2_UINT_LEAST32_C__(0xFFFFffff))
+#define LIBAR2_MAX_ADLEN ((size_t)LIBAR2_UINT_LEAST32_C__(0xFFFFffff))
+#define LIBAR2_MIN_HASHLEN ((size_t)LIBAR2_UINT_LEAST32_C__(4))
+#define LIBAR2_MAX_HASHLEN ((size_t)LIBAR2_UINT_LEAST32_C__(0xFFFFffff))
+#define LIBAR2_IS_TYPE_OK(T) ((T) == LIBAR2_ARGON2D || (T) == LIBAR2_ARGON2I || (T) == LIBAR2_ARGON2ID || (T) == LIBAR2_ARGON2DS)
+#define LIBAR2_IS_VERSION_OK(V) ((V) == LIBAR2_ARGON2_VERSION_10 || (V) == LIBAR2_ARGON2_VERSION_13)
+
+/* } */
+
+/**
+ * List all parameter errors
+ *
+ * @param X Macro to expand for each error
+ * @param P A `const struct libar2_argon2_parameters *` to inspect
+ */
+#define LIBAR2_LIST_PARAMETER_ERRORS(X, P)\
+ X(LIBAR2_T_COST_TOO_SMALL, "time-cost parameter is too small", (P)->t_cost < LIBAR2_MIN_T_COST)\
+ X(LIBAR2_T_COST_TOO_LARGE, "time-cost parameter is too large", (P)->t_cost > LIBAR2_MAX_T_COST)\
+ X(LIBAR2_M_COST_TOO_SMALL, "memory-cost parameter is too small", (P)->m_cost < LIBAR2_MIN_M_COST)\
+ X(LIBAR2_M_COST_TOO_LARGE, "memory-cost parameter is too large", (P)->m_cost > LIBAR2_MAX_M_COST)\
+ X(LIBAR2_TOO_FEW_LANES, "lane-count parameter is too small", (P)->lanes < LIBAR2_MIN_LANES)\
+ X(LIBAR2_TOO_MANY_LANES, "lane-count parameter is too large", (P)->lanes > LIBAR2_MAX_LANES)\
+ X(LIBAR2_SALT_TOO_SMALL, "salt parameter is too small", (P)->saltlen < LIBAR2_MIN_SALTLEN)\
+ X(LIBAR2_SALT_TOO_LARGE, "salt parameter is too large", (P)->saltlen > LIBAR2_MAX_SALTLEN)\
+ X(LIBAR2_KEY_TOO_LARGE, "secret parameter is too large", (P)->keylen > LIBAR2_MAX_KEYLEN)\
+ X(LIBAR2_AD_TOO_LARGE, "associated data parameter is too large", (P)->adlen > LIBAR2_MAX_ADLEN)\
+ X(LIBAR2_HASH_TOO_SMALL, "tag length parameter is too small", (P)->hashlen < LIBAR2_MIN_HASHLEN)\
+ X(LIBAR2_HASH_TOO_LARGE, "tag length parameter is too large", (P)->hashlen > LIBAR2_MAX_HASHLEN)\
+ X(LIBAR2_INVALID_TYPE, "type parameter is invalid", !LIBAR2_IS_TYPE_OK((P)->type))\
+ X(LIBAR2_INVALID_VERSION, "version parameter is invalid", !LIBAR2_IS_VERSION_OK((P)->version))
+
+/**
+ * Parameter errors
+ */
+enum libar2_parameter_error {
+
+ /**
+ * No error
+ */
+ LIBAR2_OK = 0
+
+#define LIBAR2_X__(ENUM, ERRMESG, CONDITION) ,ENUM
+ LIBAR2_LIST_PARAMETER_ERRORS(LIBAR2_X__,)
+#undef LIBAR2_X__
+};
+
+/**
+ * String case
+ */
+enum libar2_casing {
+
+ /**
+ * Lower case, e.g. "argon2i"
+ */
+ LIBAR2_LOWER_CASE = 0,
+
+ /**
+ * Title case, e.g. "Argon2i"
+ */
+ LIBAR2_TITLE_CASE = 1,
+
+ /**
+ * Upper case, e.g. "ARGON2I"
+ */
+ LIBAR2_UPPER_CASE = 2
+};
+
+/**
+ * Argon2 primitive types
+ */
+enum libar2_argon2_type {
+
+ /**
+ * Secret-dependent hashing
+ *
+ * Only for side-channel-free environment!
+ */
+ LIBAR2_ARGON2D = 0,
+
+ /**
+ * Secret-independent hashing
+ *
+ * Good for side-channels but worse with respect
+ * to trade of attacks if only one pass is used
+ */
+ LIBAR2_ARGON2I = 1,
+
+ /**
+ * Hybrid construction
+ *
+ * OK against side channels and better with
+ * respect to tradeoff attacks
+ */
+ LIBAR2_ARGON2ID = 2,
+
+ /* There is no type with value 3 */
+
+ /**
+ * Substition box (S-box)-hardened
+ */
+ LIBAR2_ARGON2DS = 4
+};
+
+/**
+ * Argon2 versions
+ */
+enum libar2_argon2_version {
+
+ /**
+ * Argon2 version 1.0 ("10")
+ */
+ LIBAR2_ARGON2_VERSION_10 = 0x10,
+
+ /**
+ * Argon2 version 1.3 ("13")
+ */
+ LIBAR2_ARGON2_VERSION_13 = 0x13
+};
+
+/**
+ * Argon2 hashing parameters
+ */
+struct libar2_argon2_parameters {
+
+ /**
+ * Primitive type
+ */
+ enum libar2_argon2_type type;
+
+ /**
+ * Version number
+ *
+ * `libar2_latest_argon2_version` is recommended
+ */
+ enum libar2_argon2_version version;
+
+ /**
+ * Number of passes
+ *
+ * At least 1, at most 2³²−1
+ */
+ uint_least32_t t_cost;
+
+ /**
+ * Amount of required memory, in kilobytes
+ *
+ * At least 8, at most MAX(2³²−1, address-space » 11)
+ */
+ uint_least32_t m_cost;
+
+ /**
+ * Number of lanes
+ *
+ * At least 1, at most 2²⁴−1
+ */
+ uint_least32_t lanes;
+
+ /**
+ * Salt, binary
+ *
+ * Only modified if `.autoerase_salt` in
+ * `struct libar2_context` is non-zero
+ */
+ unsigned char *salt;
+
+ /**
+ * The length (bytes) of the salt
+ *
+ * At least 8, at most 2³²−1
+ */
+ size_t saltlen;
+
+ /**
+ * Secret (pepper), binary [optional]
+ *
+ * Only modified if `.autoerase_secret` in
+ * `struct libar2_context` is non-zero
+ */
+ unsigned char *key;
+
+ /**
+ * The length (bytes) of the secret
+ *
+ * At least 0, at most 2³²−1
+ */
+ size_t keylen;
+
+ /**
+ * Arbitrary extra associated data, binary [optional]
+ *
+ * Only modified if `.autoerase_associated_data`
+ * in `struct libar2_context` is non-zero
+ */
+ unsigned char *ad;
+
+ /**
+ * The length (bytes) of the associated
+ *
+ * At least 0, at most 2³²−1
+ */
+ size_t adlen;
+
+ /**
+ * The length (bytes) of the output hash
+ *
+ * At least 4, at most 2³²−1
+ */
+ size_t hashlen;
+};
+
+/**
+ * Library settings
+ */
+struct libar2_context {
+
+ /**
+ * User-defined data
+ */
+ void *user_data;
+
+ /**
+ * Whether the message shall be erased
+ * immediately when it's no longer need
+ *
+ * Assumming the message is a password,
+ * you would normally set this to non-zero
+ * (properly 1), unless the password is in
+ * read-only memory for will be needed for
+ * rehashing with a stronger algorithm or
+ * new parameters
+ */
+ unsigned char autoerase_message;
+
+ /**
+ * Whether the secret shall be erased
+ * immediately when it's no longer need
+ */
+ unsigned char autoerase_secret;
+
+ /**
+ * Whether the salt shall be erased
+ * immediately when it's no longer need
+ */
+ unsigned char autoerase_salt;
+
+ /**
+ * Whether the associated data shall be
+ * erased immediately when it's no longer
+ * need
+ */
+ unsigned char autoerase_associated_data;
+
+ /**
+ * Memory allocation function
+ *
+ * It is safe to assume that `.allocate` and
+ * `.deallocate` will be called in stack order
+ * and never in threads run using `.run_thread`
+ *
+ * Example implementation:
+ *
+ * static void *
+ * allocate(size_t num, size_t size, size_t alignment, struct libar2_context *ctx)
+ * {
+ * void *ptr;
+ * int err;
+ * (void) ctx;
+ * if (num > SIZE_MAX / size) {
+ * errno = ENOMEM;
+ * return NULL;
+ * }
+ * if (alignment < sizeof(void *))
+ * alignment = sizeof(void *);
+ * err = posix_memalign(&ptr, alignment, num * size);
+ * if (err) {
+ * errno = err;
+ * return NULL;
+ * } else {
+ * return ptr;
+ * }
+ * }
+ *
+ * @param num The number of elements to allocate, never 0
+ * @param size The size of each element, never 0
+ * @param alignment Requires memory alignment, never 0
+ * @param ctx The structure containing the callback
+ * @return Pointer to the allocated memory, `NULL` on failure
+ */
+ void *(*allocate)(size_t num, size_t size, size_t alignment, struct libar2_context *ctx);
+
+ /**
+ * Memory deallocation function
+ *
+ * The application may which to earse the memory before
+ * deallocating it; this is not done by the library.
+ * This can be done using `libar2_earse`;
+ *
+ * Example implementation:
+ *
+ * static void
+ * deallocate(void *ptr, struct libar2_context *ctx)
+ * {
+ * (void) ctx;
+ * free(ptr);
+ * }
+ *
+ * @param ptr The pointer to the memory to deallocate,
+ * always a pointer returned by `.allocate`
+ * @param ctx The structure containing the callback
+ */
+ void (*deallocate)(void *ptr, struct libar2_context *ctx);
+
+ /**
+ * Initialise the thread pool
+ *
+ * If thread support is desired, but the application do not
+ * want to keep track of the threads using a thread pool,
+ * this function must store `desired` in `*createdp`. The
+ * application must also, in this case, make sure that
+ * `.join_thread_pool` returns after all started threads
+ * have stopped, and `.get_ready_threads` store unique
+ * indices within the range [0, `desired`) (start with
+ * `i = 0` and each time an index is stored, calculate it
+ * with `i++ % desired`). Alternatively, and more preferably,
+ * this scheme can be used, but adapted to limit the number
+ * of concurrent threads, keeping track of the number of
+ * running threads, and not let `.get_ready_threads` return
+ * before this number is small enough; `*createdp` must however
+ * still set to `desired` so that to threads are not running
+ * concurrently with the same memory segment as the provided
+ * argument for the function to run, as this could be a source
+ * of memory corruption. It is however recommended to implement
+ * proper thread pooling as the library will call `.run_thread`
+ * `4 * params->t_cost * params->lanes` times where `params`
+ * is the used `struct libar2_argon2_parameters *`.
+ *
+ * @param desired The number of threads that, at a maximum,
+ * will be used by the library, from the
+ * thread pool
+ * @param createdp Output parameter for the number of threads
+ * allocated, 0 if threading is disabled,
+ * which is preferable if `desired` is 1
+ * @param ctx The structure containing the callback
+ * @return 0 on success, -1 on failure (the calling
+ * function will exit indicating error and keep
+ * the value of `errno` set by this function)
+ */
+ int (*init_thread_pool)(size_t desired, size_t *createdp, struct libar2_context *ctx);
+
+ /**
+ * Wait until at least one thread in the pool is ready
+ * (may be immediately), and get some of their indices
+ *
+ * @param indices Output array for the indices of the ready threads
+ * @param n The maximum number of thread indices to store in `indices`;
+ * this number may exceed the number of created, or even
+ * requested, threads, or the number of threads the function
+ * will attempt to use
+ * @param ctx The structure containing the callback
+ * @return The number of ready threads (non-zero, but may exceed `n`);
+ * 0 on failure (the calling function will exit indicating
+ * error and keep the value of `errno` set by this function)
+ */
+ size_t (*get_ready_threads)(size_t *indices, size_t n, struct libar2_context *ctx);
+
+ /**
+ * Run a function in a thread
+ *
+ * The requested thread will be guaranteed by
+ * `.get_ready_threads` to be ready
+ *
+ * @param index The index of the thread to use
+ * @param function The function to run
+ * @param data Argument to provide to `function`
+ * @param ctx The structure containing the callback
+ * @return 0 on success, -1 on failure (the calling
+ * function will exit indicating error and keep
+ * the value of `errno` set by this function)
+ */
+ int (*run_thread)(size_t index, void (*function)(void *data), void *data, struct libar2_context *ctx);
+
+ /**
+ * Wait until all threads in the thread pool are resting
+ *
+ * @param ctx The structure containing the callback
+ * @return 0 on success, -1 on failure (the calling
+ * function will exit indicating error and keep
+ * the value of `errno` set by this function)
+ */
+ int (*join_thread_pool)(struct libar2_context *ctx);
+
+ /**
+ * Destroy the thread pool, and all threads in it
+ * (each of the will be resting)
+ *
+ * Will be called iff `.init_thread_pool` was called
+ * successfully and returned a non-zero thread
+ * count, except, not if `.join_thread_pool` or
+ * `.get_ready_threads` failed
+ *
+ * @param ctx The structure containing the callback
+ * @return 0 on success, -1 on failure (the calling
+ * function will exit indicating error and keep
+ * the value of `errno` set by this function)
+ */
+ int (*destroy_thread_pool)(struct libar2_context *ctx);
+};
+
+
+/**
+ * The latest versions of Argon2 that is supported
+ */
+extern enum libar2_argon2_version libar2_latest_argon2_version;
+
+
+/**
+ * Convert an Argon2 primitive type value to a string
+ *
+ * @param type The primitive type
+ * @param casing The case that the string shalll use
+ * @return String representing the type, `NULL` (with `errno`
+ * set to EINVAL) if either argument is invalid
+ */
+const char *libar2_type_to_string(enum libar2_argon2_type type, enum libar2_casing casing);
+
+/**
+ * Convert a string to an Argon2 primitive type value
+ *
+ * @param str String representing the primitive type
+ * @param typep Output parameter for the primitive type
+ * @return 0 on success, -1 (with `errno` set to EINVAL) if `str` is invalid
+ */
+int libar2_string_to_type(const char *str, enum libar2_argon2_type *typep);
+
+/**
+ * Convert an Argon2 version number value to a string,
+ * will be returned without a dot
+ *
+ * @param version The version number value
+ * @return String representing the version, `NULL` (with
+ * `errno` set to EINVAL) if `version` is invalid
+ */
+const char *libar2_version_to_string(enum libar2_argon2_version version);
+
+/**
+ * Convert an Argon2 version number value to a string,
+ * will be returned with a dot
+ *
+ * @param version The version number value
+ * @return String representing the version, `NULL` (with
+ * `errno` set to EINVAL) if `version` is invalid
+ */
+const char *libar2_version_to_string_proper(enum libar2_argon2_version version);
+
+/**
+ * Convert a string to an Argon2 version number value
+ *
+ * @param str String representing the version
+ * @param versionp Output parameter for the version number value
+ * @return 0 on success, -1 (with `errno` set to EINVAL) if `str` is invalid
+ */
+int libar2_string_to_version(const char *str, enum libar2_argon2_version *versionp);
+
+/**
+ * Encode hashing parameters
+ *
+ * Secret, associated data, and tag length (`params->hashlen`)
+ * will not be included in the output
+ *
+ * To encode a string with both the parameters and the
+ * hash, simply append the output of `libar2_encode_base64`
+ * over the hash onto the output of this function
+ *
+ * @param buf Output buffer, or `NULL`
+ * @param params Hashing parameters
+ * @return The number of bytes required for `buf`,
+ * including the NUL byte added to the end
+ */
+size_t libar2_encode_params(char *buf, const struct libar2_argon2_parameters *params);
+
+/**
+ * Encode data with base64 encoding, which is what
+ * Argon2 uses, rather than B64 which is what crypt(3)
+ * uses; however this version of base64 is not padded
+ * to a length divible by 4
+ *
+ * @param buf Output buffer, or `NULL`
+ * @param data The data to encode
+ * @param len The number of bytes in `data`
+ * @return The number of bytes required for `buf`,
+ * including the NUL byte added to the end
+ */
+size_t libar2_encode_base64(char *buf, const void *data, size_t len);
+
+/**
+ * Decode hashing parameters
+ *
+ * Secret and associated data will be set to zero-length
+ *
+ * It is recommended that application stores that default
+ * parameters encoded with `libar2_encode_params` using
+ * a hash of only null bytes. When the allocation decodes
+ * this string before generating the hash for a new password,
+ * it would refill the salt buffer with random bytes. The
+ * salt buffer will be allocated by this function.
+ *
+ * The tag length (`params->hashlen`) will calculated from
+ * that hash at the end of `str`, however, the encoded length
+ * of the hash will not be included in the function's return
+ * value
+ *
+ * @param str Hashing parameter string
+ * @param params Output parameter for the hashing parameters
+ * @param bufp Output parameter for buffer containing variable
+ * length data in `params`; will be allocated
+ * using `ctx->allocate`
+ * @param ctx `.allocate` and `.deallocate` must be set
+ * @return The number of bytes read, 0 if `str` is improperly
+ * formatted (EINVAL), contain an unrecognised primitive
+ * type (EINVAL), or contained a value that is too large
+ * to be stored (ERANGE) (otherwise invalid parameters
+ * are not checked); if a callback from `ctx` fails, 0
+ * is returned with `errno` set to the value set by the
+ * callback; if non-zero is returned, and `str` contains
+ * a hash, and not just parameters, `&str[return]` will
+ * point to the hash
+ */
+size_t libar2_decode_params(const char *str, struct libar2_argon2_parameters *params, char **bufp, struct libar2_context *ctx);
+
+/**
+ * Decode data encoded with base64 (padding with '=' is optional)
+ *
+ * @param str The data to decode
+ * @param data Output buffer for the decoded data, or `NULL`
+ * @param lenp Output parameter for the length of the decoded data
+ * @return The number of bytes read
+ */
+size_t libar2_decode_base64(const char *str, void *data, size_t *lenp);
+
+/**
+ * Validate hashing parameters
+ *
+ * @param params The hashing parameters
+ * @param errmsgp Output parameter for the error message, or `null`
+ * @return The first detected error, or LIBAR2_OK (0) if none
+ */
+enum libar2_parameter_error libar2_validate_params(const struct libar2_argon2_parameters *params, const char **errmsgp);
+
+/**
+ * Securily earse memory
+ *
+ * @param mem The memory to erase
+ * @param size The number of bytes to erase
+ */
+void libar2_earse(volatile void *mem, size_t size);
+
+/**
+ * Hash a message
+ *
+ * The recommended why of verify a password is to hash the
+ * provided password with this function, convert the known
+ * hash with `libar2_decode_base64` to binary (if it's not
+ * already in binary), and let `h` be the result of this
+ * function, `k` be the known password hash, and `n` be
+ * `params->hashlen`, then the password is correct iff
+ * `d`, as computed below, is 0:
+ *
+ * unsigned char d = 0;
+ * size_t i;
+ * for (i = 0; i < n; i++) {
+ * d |= h[i] ^ k[i];
+ * }
+ *
+ * It is preferable this check is done by the process that
+ * knowns the correct password hash, and that the tryed
+ * password is hashed before it is sent to that process
+ *
+ * Note that on failure, the function will not necessarily
+ * have erased data configured in `ctx` to be automatically
+ * erased
+ *
+ * @param hash Binary hash ("tag") output buffer, shall
+ * be at least `params->hashlen` bytes large
+ * @param msg Message (password) to hash; only modified if
+ * `ctx->autoerase_message` is non-zero
+ * @param msglen The length of `msg`; at least 0, at most 2³²−1
+ * @param params Hashing parameters
+ * @param ctx Library settings
+ * @return 0 on success, -1 on failure
+ */
+int libar2_hash(void *hash, void *msg, size_t msglen, struct libar2_argon2_parameters *params, struct libar2_context *ctx);
+
+#endif
diff --git a/libar2_decode_base64.c b/libar2_decode_base64.c
new file mode 100644
index 0000000..df35a60
--- /dev/null
+++ b/libar2_decode_base64.c
@@ -0,0 +1,68 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+#define FF 0xFF
+static const unsigned char lut[256] = {
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, 62, FF, FF, FF, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, FF, FF, FF, FF, FF, FF,
+ FF, 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, FF, FF, FF, FF, FF,
+ FF, 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, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF,
+ FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF
+};
+
+
+size_t
+libar2_decode_base64(const char *str_, void *data_, size_t *lenp)
+{
+ const unsigned char *str = (const unsigned char *)str_;
+ unsigned char *data = data_;
+ unsigned char a, b, c, d;
+ size_t ret = 0;
+
+ *lenp = 0;
+
+ for(;; str += 4) {
+ if (lut[str[0]] == FF || lut[str[1]] == FF)
+ break;
+ a = lut[str[0]];
+ b = lut[str[1]];
+ ret += 2;
+ if (data)
+ *data++ = (unsigned char)((a << 2) | (b >> 4));
+ ++*lenp;
+
+ if (lut[str[2]] == FF) {
+ ret += (str[2] == '=');
+ ret += (str[2] == '=' && str[3] == '=');
+ break;
+ }
+ c = lut[str[2]];
+ ret += 1;
+ if (data)
+ *data++ = (unsigned char)((b << 4) | (c >> 2));
+ ++*lenp;
+
+ if (lut[str[3]] == FF) {
+ ret += (str[3] == '=');
+ break;
+ }
+ d = lut[str[3]];
+ ret += 1;
+ if (data)
+ *data++ = (unsigned char)((c << 6) | (d >> 0));
+ ++*lenp;
+ }
+
+ return ret;
+}
diff --git a/libar2_decode_params.c b/libar2_decode_params.c
new file mode 100644
index 0000000..cc90c24
--- /dev/null
+++ b/libar2_decode_params.c
@@ -0,0 +1,147 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static size_t
+decode_u32(const char *s, uint_least32_t *outp)
+{
+ uint_least32_t digit;
+ size_t i;
+
+ if ((s[0] == '0' && s[1] == '0') || !isdigit(s[0])) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ *outp = 0;
+ for (i = 0; isdigit(s[i]); i++) {
+ digit = (uint_least32_t)(s[i] & 15);
+ if (*outp > (UINT_LEAST32_C(0xFFFFffff) - digit) / 10) {
+ errno = ERANGE;
+ return 0;
+ }
+ *outp = *outp * 10 + digit;
+ }
+
+ return i;
+}
+
+
+size_t
+libar2_decode_params(const char *str, struct libar2_argon2_parameters *params, char **bufp, struct libar2_context *ctx)
+{
+ const char *start = str;
+ uint_least32_t u32, *u32p;
+ int have_t = 0, have_m = 0, have_p = 0;
+ size_t n, q, r;
+
+ *bufp = NULL;
+ params->salt = NULL;
+ params->saltlen = 0;
+ params->key = NULL;
+ params->keylen = 0;
+ params->ad = NULL;
+ params->adlen = 0;
+
+ if (*str++ != '$')
+ goto einval;
+
+ if (libar2_string_to_type(str, &params->type))
+ goto fail;
+ while (*str && *str != '$')
+ str++;
+
+ if (*str++ != '$')
+ goto einval;
+
+ if (str[0] == 'v' && str[1] == '=') {
+ n = decode_u32(&str[2], &u32);
+ if (!n)
+ goto fail;
+ if (u32 > (uint_least32_t)INT_MAX)
+ goto erange;
+ params->version = (enum libar2_argon2_version)u32;
+ str += n + 2;
+ if (*str++ != '$')
+ goto einval;
+ } else {
+ params->version = LIBAR2_ARGON2_VERSION_10;
+ }
+
+ while (*str && *str != '$') {
+ if (str[0] == 't' && str[1] == '=') {
+ if (have_t)
+ goto einval;
+ have_t = 1;
+ u32p = &params->t_cost;
+ str += 2;
+
+ } else if (str[0] == 'm' && str[1] == '=') {
+ if (have_m)
+ goto einval;
+ have_m = 1;
+ u32p = &params->m_cost;
+ str += 2;
+
+ } else if (str[0] == 'p' && str[1] == '=') {
+ if (have_p)
+ goto einval;
+ have_p = 1;
+ u32p = &params->lanes;
+ str += 2;
+
+ } else {
+ goto einval;
+ }
+
+ n = decode_u32(str, u32p);
+ if (!n)
+ goto fail;
+ str += n;
+ if (*str == '$')
+ break;
+ if (*str != ',')
+ goto einval;
+ str++;
+ }
+
+ if (have_t + have_m + have_p != 3)
+ goto einval;
+
+ if (*str++ != '$')
+ goto einval;
+
+ n = libar2_decode_base64(str, NULL, &params->saltlen);
+ if (params->saltlen) {
+ *bufp = ctx->allocate(params->saltlen, sizeof(char), ALIGNOF(char), ctx);
+ if (!*bufp)
+ goto fail;
+ }
+ str += libar2_decode_base64(str, *bufp, &params->saltlen);
+ params->salt = (void *)*bufp;
+
+ if (*str++ != '$')
+ goto einval;
+
+ params->hashlen = 0;
+ while (isalnum(str[params->hashlen]) || str[params->hashlen] == '+' || str[params->hashlen] == '/')
+ params->hashlen += 1;
+ q = params->hashlen / 4;
+ r = params->hashlen % 4;
+ params->hashlen = q * 3 + (r == 3 ? 2 : r == 2 ? 1 : 0);
+
+ return (size_t)(str - start);
+
+einval:
+ errno = EINVAL;
+ goto fail;
+
+erange:
+ errno = ERANGE;
+fail:
+ if (*bufp) {
+ ctx->deallocate(*bufp, ctx);
+ *bufp = NULL;
+ }
+ return 0;
+}
diff --git a/libar2_earse.c b/libar2_earse.c
new file mode 100644
index 0000000..905695a
--- /dev/null
+++ b/libar2_earse.c
@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+#if defined(memset_s)
+#elif defined(explicit_bzero) || defined(__OpenBSD__)
+#elif defined(explicit_memset)
+#else
+# if defined(__GNUC__)
+__attribute__((visibility("hidden")))
+# endif
+void *(*const volatile libar2_internal_explicit_memset__)(void *, int, size_t) = &memset;
+#endif
+
+
+#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
+libar2_earse(volatile void *mem_, size_t size)
+{
+ void *mem = *(void **)(void *)&mem_;
+#if defined(memset_s)
+ memset_s(mem, size);
+#elif defined(explicit_bzero) || defined(__OpenBSD__)
+ explicit_bzero(mem, size);
+#elif defined(explicit_memset)
+ explicit_memset(mem, 0, size);
+#else
+ libar2_internal_explicit_memset__(mem, 0, size);
+#endif
+}
diff --git a/libar2_encode_base64.c b/libar2_encode_base64.c
new file mode 100644
index 0000000..857b3e8
--- /dev/null
+++ b/libar2_encode_base64.c
@@ -0,0 +1,42 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+#define ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+static char lut[256] = ALPHABET ALPHABET ALPHABET ALPHABET;
+
+#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
+libar2_encode_base64(char *buf, const void *data_, size_t len)
+{
+ const unsigned char *data = data_;
+ size_t q, r, i;
+
+ q = len / 3;
+ r = len % 3;
+
+ if (buf) {
+ for (i = 0; i < q; i++, data += 3) {
+ *buf++ = lut[O1(data[0], data[1], data[2]) & 255];
+ *buf++ = lut[O2(data[0], data[1], data[2]) & 255];
+ *buf++ = lut[O3(data[0], data[1], data[2]) & 255];
+ *buf++ = lut[O4(data[0], data[1], data[2]) & 255];
+ }
+ if (r == 1) {
+ *buf++ = lut[O1(data[0], 0, 0) & 255];
+ *buf++ = lut[O2(data[0], 0, 0) & 255];
+ } else if (r == 2) {
+ *buf++ = lut[O1(data[0], data[1], 0) & 255];
+ *buf++ = lut[O2(data[0], data[1], 0) & 255];
+ *buf++ = lut[O3(data[0], data[1], 0) & 255];
+ }
+ *buf = '\0';
+ }
+
+ return (q * 4) + r + !!r + 1;
+}
diff --git a/libar2_encode_params.c b/libar2_encode_params.c
new file mode 100644
index 0000000..cdf634a
--- /dev/null
+++ b/libar2_encode_params.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+size_t
+libar2_encode_params(char *buf, const struct libar2_argon2_parameters *params)
+{
+ size_t off;
+
+#define FMT_AND_ARGS\
+ "$%s$v=%i$m=%ju,t=%ju,p=%ju$",\
+ libar2_type_to_string(params->type, LIBAR2_LOWER_CASE),\
+ (int)params->version,\
+ (uintmax_t)params->m_cost,\
+ (uintmax_t)params->t_cost,\
+ (uintmax_t)params->lanes
+
+ if (buf) {
+ off = (size_t)sprintf(buf, FMT_AND_ARGS);
+ off += libar2_encode_base64(&buf[off], params->salt, params->saltlen) - 1;
+ buf[off++] = '$';
+ buf[off++] = '\0';
+
+ } else {
+ off = (size_t)snprintf(NULL, 0, FMT_AND_ARGS);
+ off += libar2_encode_base64(NULL, params->salt, params->saltlen) - 1;
+ off += 2;
+ }
+
+ return off;
+}
diff --git a/libar2_hash.c b/libar2_hash.c
new file mode 100644
index 0000000..0eb9211
--- /dev/null
+++ b/libar2_hash.c
@@ -0,0 +1,612 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+struct threaded_fill_segments_params {
+ struct block *memory;
+ const uint_least64_t *sbox;
+ struct libar2_argon2_parameters *params;
+ uint_least32_t seglen;
+ uint_least32_t lanelen;
+ uint_least32_t blocks;
+ uint_least32_t pass;
+ uint_least32_t lane;
+ uint_least32_t slice;
+};
+
+
+static const struct libblake_blake2b_params b2params = {
+ .digest_len = 64,
+ .key_len = 0,
+ .fanout = 1,
+ .depth = 1,
+ .leaf_len = 0,
+ .node_offset = 0,
+ .node_depth = 0,
+ .inner_len = 0
+};
+
+
+static const struct block zerob; /* implicitly zeroed via `static` */
+
+
+static void
+memxor(void *a_, const void *b_, size_t n)
+{
+ unsigned char *a = a_;
+ const unsigned char *b = b_;
+ size_t i;
+ for (i = 0; i < n; i++)
+ a[i] ^= b[i];
+}
+
+
+static size_t
+store32(unsigned char *out, uint_least32_t value)
+{
+ out[0] = (unsigned char)((value >> 0) & 255);
+ out[1] = (unsigned char)((value >> 8) & 255);
+ out[2] = (unsigned char)((value >> 16) & 255);
+ out[3] = (unsigned char)((value >> 24) & 255);
+ return 4;
+}
+
+
+static void
+store64(unsigned char *out, uint_least64_t value)
+{
+ out[0] = (unsigned char)((value >> 0) & 255);
+ out[1] = (unsigned char)((value >> 8) & 255);
+ out[2] = (unsigned char)((value >> 16) & 255);
+ out[3] = (unsigned char)((value >> 24) & 255);
+ out[4] = (unsigned char)((value >> 32) & 255);
+ out[5] = (unsigned char)((value >> 40) & 255);
+ out[6] = (unsigned char)((value >> 48) & 255);
+ out[7] = (unsigned char)((value >> 56) & 255);
+}
+
+
+static void
+load64(uint_least64_t *out, const unsigned char *data)
+{
+ *out = ((uint_least64_t)(data[0] & 255) << 0)
+ | ((uint_least64_t)(data[1] & 255) << 8)
+ | ((uint_least64_t)(data[2] & 255) << 16)
+ | ((uint_least64_t)(data[3] & 255) << 24)
+ | ((uint_least64_t)(data[4] & 255) << 32)
+ | ((uint_least64_t)(data[5] & 255) << 40)
+ | ((uint_least64_t)(data[6] & 255) << 48)
+ | ((uint_least64_t)(data[7] & 255) << 56);
+}
+
+
+static void
+store_block(unsigned char *block8, const struct block *block64)
+{
+ size_t i, j;
+ for (i = 0, j = 0; i < 1024; i += 8, j += 1)
+ store64(&block8[i], block64->w[j]);
+}
+
+
+static void
+load_block(struct block *block64, const unsigned char *block8)
+{
+ size_t i, j;
+ for (i = 0, j = 0; i < 1024; i += 8, j += 1)
+ load64(&block64->w[j], &block8[i]);
+}
+
+
+static size_t
+storemem(unsigned char *out, const void *mem, size_t len, size_t max)
+{
+ size_t n = MIN(len, max);
+ memcpy(out, mem, n);
+ return n;
+}
+
+
+static uint_least64_t
+rotr64(uint_least64_t x, int n)
+{
+ return ((x >> n) | (x << (64 - n))) & UINT_LEAST64_C(0xFFFFffffFFFFffff);
+}
+
+
+static uint_least64_t
+fBlaMka(uint_least64_t x, uint_least64_t y)
+{
+ return x + y + 2 * (x & UINT_LEAST64_C(0xFFffFFff)) * (y & UINT_LEAST64_C(0xFFffFFff));
+}
+
+
+static void
+fill_block(struct block *block, const struct block *prevblock, const struct block *refblock,
+ int with_xor, const uint_least64_t *sbox)
+{
+ uint_least64_t x = 0;
+ uint_least32_t x_hi, x_lo;
+ struct block tmpblock;
+ size_t i;
+
+ if (with_xor) {
+ for (i = 0; i < ELEMSOF(refblock->w); i++)
+ block->w[i] ^= tmpblock.w[i] = refblock->w[i] ^ prevblock->w[i];
+ } else {
+ for (i = 0; i < ELEMSOF(refblock->w); i++)
+ block->w[i] = tmpblock.w[i] = refblock->w[i] ^ prevblock->w[i];
+ }
+
+ if (sbox) {
+ x = tmpblock.w[0] ^ tmpblock.w[ELEMSOF(tmpblock.w) - 1];
+ for (i = 0; i < 96; i++) {
+ x_hi = (uint_least32_t)(x >> 32);
+ x_lo = (uint_least32_t)x & UINT_LEAST32_C(0xFFFFffff);
+ x = (uint_least64_t)x_hi * (uint_least64_t)x_lo;
+ x += sbox[(x_hi & UINT_LEAST32_C(0x1FF)) + 0];
+ x ^= sbox[(x_lo & UINT_LEAST32_C(0x1FF)) + 512];
+ }
+ }
+
+#define BLAMKA_G(A, B, C, D)\
+ A = fBlaMka(A, B);\
+ D = rotr64(D ^ A, 32);\
+ C = fBlaMka(C, D);\
+ B = rotr64(B ^ C, 24);\
+ A = fBlaMka(A, B);\
+ D = rotr64(D ^ A, 16);\
+ C = fBlaMka(C, D);\
+ B = rotr64(B ^ C, 63)
+
+#define BLAMKA_ROUND(W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, WA, WB, WC, WD, WE, WF)\
+ BLAMKA_G(W0, W4, W8, WC);\
+ BLAMKA_G(W1, W5, W9, WD);\
+ BLAMKA_G(W2, W6, WA, WE);\
+ BLAMKA_G(W3, W7, WB, WF);\
+ BLAMKA_G(W0, W5, WA, WF);\
+ BLAMKA_G(W1, W6, WB, WC);\
+ BLAMKA_G(W2, W7, W8, WD);\
+ BLAMKA_G(W3, W4, W9, WE)
+
+#define BLAMKA_ROUND_(ARR, OFF, W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, WA, WB, WC, WD, WE, WF)\
+ BLAMKA_ROUND(ARR[OFF + W0], ARR[OFF + W1], ARR[OFF + W2], ARR[OFF + W3],\
+ ARR[OFF + W4], ARR[OFF + W5], ARR[OFF + W6], ARR[OFF + W7],\
+ ARR[OFF + W8], ARR[OFF + W9], ARR[OFF + WA], ARR[OFF + WB],\
+ ARR[OFF + WC], ARR[OFF + WD], ARR[OFF + WE], ARR[OFF + WF])
+
+ for (i = 0; i < 8; i++) {
+ BLAMKA_ROUND_(tmpblock.w, i * 16,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 12, 13, 14, 15);
+ }
+ for (i = 0; i < 8; i++) {
+ BLAMKA_ROUND_(tmpblock.w, i * 2,
+ 0, 1, 16, 17,
+ 32, 33, 48, 49,
+ 64, 65, 80, 81,
+ 96, 97, 112, 113);
+ }
+
+ for (i = 0; i < ELEMSOF(refblock->w); i++)
+ block->w[i] ^= tmpblock.w[i];
+
+ block->w[0] += x;
+ block->w[ELEMSOF(block->w) - 1] += x;
+ block->w[0] &= UINT_LEAST64_C(0xFFFFffffFFFFffff);
+ block->w[ELEMSOF(block->w) - 1] &= UINT_LEAST64_C(0xFFFFffffFFFFffff);
+}
+
+
+static void
+generate_sbox(uint_least64_t *sbox, struct block *memory)
+{
+ void *next, *prev = memory;
+ size_t i;
+
+ for (i = 0; i < 8; i++) {
+ next = &sbox[i * 128];
+ fill_block(next, &zerob, prev, 0, NULL);
+ fill_block(next, &zerob, next, 0, NULL);
+ prev = next;
+ }
+}
+
+
+static void
+next_address_block(struct block *addrb, struct block *inputb)
+{
+ inputb->w[6] += 1;
+ fill_block(addrb, &zerob, inputb, 0, NULL);
+ fill_block(addrb, &zerob, addrb, 0, NULL);
+}
+
+
+static uint_least32_t
+get_rindex(uint_least32_t seglen, uint_least32_t lanelen, uint_least32_t pass,
+ uint_least32_t slice, uint_least32_t index, uint_least64_t prand, int same_lane)
+{
+ uint_least32_t size, startpos;
+ uint_least64_t relpos;
+
+ if (!pass) {
+ if (!slice)
+ size = index - 1;
+ else if (same_lane)
+ size = slice * seglen + index - 1;
+ else
+ size = slice * seglen - !index;
+ } else {
+ if (same_lane)
+ size = lanelen - seglen + index - 1;
+ else
+ size = lanelen - seglen - !index;
+ }
+
+ prand &= UINT_LEAST64_C(0xFFffFFff);
+ relpos = (prand * prand) >> 32;
+ relpos = ((uint_least64_t)size * relpos) >> 32;
+ relpos = (uint_least64_t)size - 1 - relpos;
+
+ startpos = pass ? slice == 3 ? 0 : (slice + 1) * seglen : 0;
+
+ return (startpos + (uint_least32_t)relpos) % lanelen;
+}
+
+
+static void
+fill_segment(struct block *memory, const uint_least64_t *sbox, struct libar2_argon2_parameters *params,
+ uint_least32_t seglen, uint_least32_t lanelen, uint_least32_t blocks,
+ uint_least32_t pass, uint_least32_t lane, uint_least32_t slice)
+{
+ int data_independent;
+ struct block inputb, addrb;
+ uint_least32_t off, prevoff, rlane, rindex;
+ uint_least32_t index = 0, i;
+ uint_least64_t prand;
+
+ data_independent =
+ (params->type == LIBAR2_ARGON2I) ||
+ (params->type == LIBAR2_ARGON2ID && !pass && slice < 2);
+
+ if (data_independent) {
+ memset(&inputb.w[6], 0, sizeof(*inputb.w) * (ELEMSOF(inputb.w) - 6));
+ inputb.w[0] = pass;
+ inputb.w[1] = lane;
+ inputb.w[2] = slice;
+ inputb.w[3] = blocks;
+ inputb.w[4] = params->t_cost;
+ inputb.w[5] = (uint_least32_t)params->type;
+ }
+
+ if (!pass && !slice) {
+ if (data_independent) {
+ next_address_block(&addrb, &inputb);
+ }
+ index = 2;
+ }
+
+ off = lane * lanelen + slice * seglen + index;
+ prevoff = off - 1 + (off % lanelen ? 0 : lanelen);
+
+ for (; index < seglen; index++, off++, prevoff++) {
+ if (off % lanelen == 1)
+ prevoff = off - 1;
+ if (data_independent) {
+ i = index % ELEMSOF(addrb.w);
+ if (!i) {
+ next_address_block(&addrb, &inputb);
+ }
+ prand = addrb.w[i];
+ } else {
+ prand = memory[prevoff].w[0];
+ }
+
+ rlane = (!pass && !slice) ? lane : (uint_least32_t)(prand >> 32) % params->lanes;
+ rindex = get_rindex(seglen, lanelen, pass, slice, index, prand, rlane == lane);
+
+ fill_block(&memory[off], &memory[prevoff], &memory[rlane * lanelen + rindex],
+ params->version > LIBAR2_ARGON2_VERSION_10 && pass, sbox);
+ }
+}
+
+
+static void
+threaded_fill_segment(void *data)
+{
+ struct threaded_fill_segments_params *tparams = data;
+ fill_segment(tparams->memory, tparams->sbox, tparams->params,
+ tparams->seglen, tparams->lanelen, tparams->blocks,
+ tparams->pass, tparams->lane, tparams->slice);
+}
+
+
+static void
+initial_hash(unsigned char hash[static 64], void *msg, size_t msglen,
+ struct libar2_argon2_parameters *params, struct libar2_context *ctx)
+{
+#define SEGMENT(DATA, LEN, OFF) &((const unsigned char *)(DATA))[(OFF)], (LEN) - (OFF)
+
+ struct libblake_blake2b_state state;
+ unsigned char block[128 + 3];
+ size_t n = 0, off;
+
+ libblake_blake2b_init(&state, &b2params, NULL);
+
+ n += store32(&block[n], params->lanes);
+ n += store32(&block[n], (uint_least32_t)params->hashlen);
+ n += store32(&block[n], params->m_cost);
+ n += store32(&block[n], params->t_cost);
+ n += store32(&block[n], (uint_least32_t)params->version);
+ n += store32(&block[n], (uint_least32_t)params->type);
+ n += store32(&block[n], (uint_least32_t)msglen);
+ if (msglen) {
+ n += off = storemem(&block[n], msg, msglen, 128 - n);
+ if (n == 128) {
+ libblake_blake2b_force_update(&state, block, n);
+ n = 0;
+ if (off < msglen) {
+ off += libblake_blake2b_force_update(&state, SEGMENT(msg, msglen, off));
+ memcpy(block, SEGMENT(msg, msglen, off));
+ n = msglen - off;
+ }
+ }
+ if (ctx->autoerase_message)
+ ERASE(msg, msglen);
+ }
+
+ n += store32(&block[n], (uint_least32_t)params->saltlen);
+ if (n >= 128) {
+ n -= libblake_blake2b_force_update(&state, block, n);
+ memcpy(block, &block[128], n); /* overlap is impossible */
+ }
+ if (params->saltlen) {
+ if (!n)
+ off = 0;
+ else
+ n += off = storemem(&block[n], params->salt, params->saltlen, 128 - n);
+ if (n == 128) {
+ libblake_blake2b_force_update(&state, block, n);
+ n = 0;
+ }
+ if (n == 0 && off < params->saltlen) {
+ off += libblake_blake2b_force_update(&state, SEGMENT(params->salt, params->saltlen, off));
+ memcpy(block, SEGMENT(params->salt, params->saltlen, off));
+ n = params->saltlen - off;
+ }
+ if (ctx->autoerase_salt)
+ ERASE(params->salt, params->saltlen);
+ }
+
+ n += store32(&block[n], (uint_least32_t)params->keylen);
+ if (n >= 128) {
+ n -= libblake_blake2b_force_update(&state, block, n);
+ memcpy(block, &block[128], n); /* overlap is impossible */
+ }
+ if (params->keylen) {
+ if (!n)
+ off = 0;
+ else
+ n += off = storemem(&block[n], params->key, params->keylen, 128 - n);
+ if (n == 128) {
+ libblake_blake2b_force_update(&state, block, n);
+ n = 0;
+ }
+ if (n == 0 && off < params->keylen) {
+ off += libblake_blake2b_force_update(&state, SEGMENT(params->key, params->keylen, off));
+ memcpy(block, SEGMENT(params->key, params->keylen, off));
+ n = params->keylen - off;
+ }
+ if (ctx->autoerase_secret)
+ ERASE(params->key, params->keylen);
+ }
+
+ n += store32(&block[n], (uint_least32_t)params->adlen);
+ if (n > 128 || (n == 128 && params->adlen)) {
+ n -= libblake_blake2b_force_update(&state, block, n);
+ memcpy(block, &block[128], n); /* overlap is impossible */
+ }
+ if (params->adlen) {
+ if (!n)
+ off = 0;
+ else
+ n += off = storemem(&block[n], params->ad, params->adlen, 128 - n);
+ if (off < params->adlen) {
+ if (n == 128) {
+ libblake_blake2b_force_update(&state, block, n);
+ n = 0;
+ }
+ if (n == 0) {
+ off += libblake_blake2b_update(&state, SEGMENT(params->ad, params->adlen, off));
+ if (params->adlen - off > 128)
+ off += libblake_blake2b_force_update(&state, SEGMENT(params->ad, params->adlen, off));
+ memcpy(block, SEGMENT(params->ad, params->adlen, off));
+ n = params->adlen - off;
+ }
+ }
+ if (ctx->autoerase_associated_data)
+ ERASE(params->ad, params->adlen);
+ }
+
+ libblake_blake2b_digest(&state, block, n, 0, 64, hash);
+
+ ERASE_ARRAY(block);
+ ERASE_STRUCT(state);
+
+#undef SEGMENT
+}
+
+
+static void /* this is not BLAKE2Xb, but something Argon2-specific */
+argon2_blake2b_exthash(void *hash_, size_t hashlen, void *msg_, size_t msglen)
+{
+ struct libblake_blake2b_params params;
+ struct libblake_blake2b_state state;
+ unsigned char *msg = msg_;
+ unsigned char block[128];
+ unsigned char *hash = hash_;
+ size_t n, off;
+
+ params = b2params;
+ params.digest_len = (uint_least8_t)MIN(hashlen, (size_t)params.digest_len);
+
+ libblake_blake2b_init(&state, &params, NULL);
+ n = store32(block, (uint_least32_t)hashlen);
+ n += off = storemem(&block[n], msg, msglen, 128 - n);
+ if (off == msglen) {
+ libblake_blake2b_digest(&state, block, n, 0, params.digest_len, hash);
+ } else {
+ libblake_blake2b_force_update(&state, block, 128);
+ libblake_blake2b_digest(&state, &msg[off], msglen - off, 0, params.digest_len, hash);
+ }
+
+ if (hashlen > 64) {
+ hashlen -= 32;
+ params.digest_len = 64;
+ while (hashlen > 64) {
+ libblake_blake2b_init(&state, &params, NULL);
+ libblake_blake2b_digest(&state, hash, 64, 0, 64, &hash[32]);
+ hash += 32;
+ hashlen -= 32;
+ }
+ params.digest_len = (uint_least8_t)hashlen;
+ libblake_blake2b_init(&state, &params, NULL);
+ libblake_blake2b_digest(&state, hash, 64, 0, hashlen, &hash[32]);
+ }
+
+ ERASE_STRUCT(state);
+ ERASE_ARRAY(block);
+}
+
+
+int
+libar2_hash(void *hash, void *msg, size_t msglen, struct libar2_argon2_parameters *params, struct libar2_context *ctx)
+{
+ unsigned char block[1024 + 128], hash0[256];
+ uint_least32_t blocks, seglen, lanelen;
+ struct block *memory;
+ size_t i, p, s, nthreads, ts[16], ti, tn;
+ struct threaded_fill_segments_params *tparams = NULL;
+ uint_least64_t *sbox = NULL; /* This is 8K large (assuming support for uint64_t), so we allocate it dynamically */
+
+ if (libar2_validate_params(params, NULL) || msglen >> 31 > 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ blocks = MAX(params->m_cost, 8 * params->lanes);
+ seglen = blocks / (4 * params->lanes);
+ blocks -= blocks % (4 * params->lanes);
+ lanelen = seglen * 4;
+
+ memory = ctx->allocate(blocks, sizeof(struct block), MAX(ALIGNOF(struct block), CACHE_LINE_SIZE), ctx);
+ if (!memory)
+ return -1;
+
+ if (params->type == LIBAR2_ARGON2DS) {
+ sbox = ctx->allocate(1024, sizeof(*sbox), ALIGNOF(uint_least64_t), ctx);
+ if (!sbox) {
+ ctx->deallocate(memory, ctx);
+ return -1;
+ }
+ }
+
+ initial_hash(hash0, msg, msglen, params, ctx);
+ for (i = 0; i < params->lanes; i++) {
+ store32(&hash0[64], 0);
+ store32(&hash0[68], (uint_least32_t)i);
+ argon2_blake2b_exthash(block, 1024, hash0, 72);
+ load_block(&memory[i * lanelen + 0], block); /* TODO this is a copy function on LE-machines */
+
+ store32(&hash0[64], 1);
+ argon2_blake2b_exthash(block, 1024, hash0, 72);
+ load_block(&memory[i * lanelen + 1], block);
+ }
+
+ ERASE_ARRAY(hash0);
+
+ if (ctx->init_thread_pool(params->lanes, &nthreads, ctx))
+ goto fail;
+ if (nthreads == 1) {
+ nthreads = 0;
+ if (ctx->destroy_thread_pool(ctx))
+ goto fail;
+ }
+
+ if (!nthreads) {
+ for (p = 0; p < params->t_cost; p++) {
+ if (sbox)
+ generate_sbox(sbox, memory);
+ for (s = 0; s < 4; s++) {
+ for (i = 0; i < params->lanes; i++) {
+ fill_segment(memory, sbox, params, seglen, lanelen, blocks,
+ (uint_least32_t)p, (uint_least32_t)i, (uint_least32_t)s);
+ }
+ }
+ }
+
+ } else {
+ tparams = ctx->allocate(nthreads, sizeof(*tparams), ALIGNOF(struct threaded_fill_segments_params), ctx);
+ if (!tparams) {
+ ctx->destroy_thread_pool(ctx);
+ goto fail;
+ }
+ for (i = 0; i < nthreads; i++) {
+ tparams[i].memory = memory;
+ tparams[i].sbox = sbox;
+ tparams[i].params = params;
+ tparams[i].seglen = seglen;
+ tparams[i].lanelen = lanelen;
+ tparams[i].blocks = blocks;
+ }
+
+ for (p = 0; p < params->t_cost; p++) {
+ if (sbox)
+ generate_sbox(sbox, memory);
+ for (s = 0; s < 4; s++) {
+ ti = tn = 0;
+ for (i = 0; i < params->lanes; i++) {
+ if (ti == tn) {
+ tn = ctx->get_ready_threads(ts, ELEMSOF(ts), ctx);
+ if (!tn)
+ goto fail;
+ }
+ tparams[ti].pass = (uint_least32_t)p;
+ tparams[ti].lane = (uint_least32_t)i;
+ tparams[ti].slice = (uint_least32_t)s;
+ if (ctx->run_thread(ts[ti], threaded_fill_segment, &tparams[ti], ctx))
+ goto fail;
+ ti++;
+ }
+ if (ctx->join_thread_pool(ctx))
+ goto fail;
+ }
+ }
+
+ if (ctx->destroy_thread_pool(ctx))
+ goto fail;
+ ctx->deallocate(tparams, ctx);
+ tparams = NULL;
+ }
+
+ for (i = 1; i < params->lanes; i++)
+ memxor(&memory[lanelen - 1], &memory[i * lanelen + lanelen - 1], sizeof(*memory));
+ store_block(block, &memory[lanelen - 1]);
+ argon2_blake2b_exthash(hash, params->hashlen, block, 1024);
+
+ ERASE_ARRAY(block);
+ if (sbox)
+ ctx->deallocate(sbox, ctx);
+ ctx->deallocate(memory, ctx);
+ return 0;
+
+fail:
+ if (tparams)
+ ctx->deallocate(tparams, ctx);
+ if (sbox)
+ ctx->deallocate(sbox, ctx);
+ ctx->deallocate(memory, ctx);
+ return -1;
+}
diff --git a/libar2_latest_argon2_version.c b/libar2_latest_argon2_version.c
new file mode 100644
index 0000000..043441d
--- /dev/null
+++ b/libar2_latest_argon2_version.c
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+enum libar2_argon2_version libar2_latest_argon2_version = LIBAR2_ARGON2_VERSION_13;
diff --git a/libar2_string_to_type.c b/libar2_string_to_type.c
new file mode 100644
index 0000000..98db6a8
--- /dev/null
+++ b/libar2_string_to_type.c
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libar2_string_to_type(const char *str, enum libar2_argon2_type *typep)
+{
+#define STRSTARTS(A, B) (!strncmp(A, B, sizeof(B) - 1))
+
+ if (!strcasecmp(str, "argon2d") || STRSTARTS(str, "argon2d$")) {
+ *typep = LIBAR2_ARGON2D;
+ return 0;
+
+ } else if (!strcasecmp(str, "argon2i") || STRSTARTS(str, "argon2i$")) {
+ *typep = LIBAR2_ARGON2I;
+ return 0;
+
+ } else if (!strcasecmp(str, "argon2id") || STRSTARTS(str, "argon2id$")) {
+ *typep = LIBAR2_ARGON2ID;
+ return 0;
+
+ } else if (!strcasecmp(str, "argon2ds") || STRSTARTS(str, "argon2ds$")) {
+ *typep = LIBAR2_ARGON2DS;
+ return 0;
+
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
diff --git a/libar2_string_to_version.c b/libar2_string_to_version.c
new file mode 100644
index 0000000..a1e6370
--- /dev/null
+++ b/libar2_string_to_version.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+int
+libar2_string_to_version(const char *str, enum libar2_argon2_version *versionp)
+{
+ if (!strcmp(str, "10") || !strcmp(str, "1.0")) {
+ *versionp = LIBAR2_ARGON2_VERSION_10;
+ return 0;
+
+ } else if (!strcmp(str, "13") || !strcmp(str, "1.3")) {
+ *versionp = LIBAR2_ARGON2_VERSION_13;
+ return 0;
+
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+}
diff --git a/libar2_type_to_string.c b/libar2_type_to_string.c
new file mode 100644
index 0000000..3092101
--- /dev/null
+++ b/libar2_type_to_string.c
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+const char *
+libar2_type_to_string(enum libar2_argon2_type type, enum libar2_casing casing)
+{
+ static const char *strs[3][5] = {
+ {"argon2d", "argon2i", "argon2id", NULL, "argon2ds"},
+ {"Argon2d", "Argon2i", "Argon2id", NULL, "Argon2ds"},
+ {"ARGON2D", "ARGON2I", "ARGON2ID", NULL, "ARGON2DS"}
+ };
+
+ if (type < 0 || casing < 0 || type >= ELEMSOF(*strs) || casing >= ELEMSOF(strs) || !strs[casing][type]) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return strs[casing][type];
+}
diff --git a/libar2_validate_params.c b/libar2_validate_params.c
new file mode 100644
index 0000000..02d159a
--- /dev/null
+++ b/libar2_validate_params.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+enum libar2_parameter_error
+libar2_validate_params(const struct libar2_argon2_parameters *params, const char **errmsgp)
+{
+#define LIBAR2_X__(ENUM, ERRMESG, CONDITION)\
+ if (CONDITION) {\
+ if (errmsgp)\
+ *errmsgp = ERRMESG;\
+ return ENUM;\
+ }
+ LIBAR2_LIST_PARAMETER_ERRORS(LIBAR2_X__, params);
+#undef LIBAR2_X__
+
+ if (errmsgp)
+ *errmsgp = "OK";
+ return LIBAR2_OK;
+}
diff --git a/libar2_version_to_string.c b/libar2_version_to_string.c
new file mode 100644
index 0000000..8783a4a
--- /dev/null
+++ b/libar2_version_to_string.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+const char *
+libar2_version_to_string(enum libar2_argon2_version version)
+{
+ if (version == LIBAR2_ARGON2_VERSION_10) {
+ return "10";
+
+ } else if (version == LIBAR2_ARGON2_VERSION_13) {
+ return "13";
+
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+}
diff --git a/libar2_version_to_string_proper.c b/libar2_version_to_string_proper.c
new file mode 100644
index 0000000..109b135
--- /dev/null
+++ b/libar2_version_to_string_proper.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+const char *
+libar2_version_to_string_proper(enum libar2_argon2_version version)
+{
+ if (version == LIBAR2_ARGON2_VERSION_10) {
+ return "1.0";
+
+ } else if (version == LIBAR2_ARGON2_VERSION_13) {
+ return "1.3";
+
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+}
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..d016d31
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,4 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..bd92de6
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,4 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..e9602e1
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,4 @@
+LIBEXT = dll
+LIBFLAGS = -mdll
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..1e99961
--- /dev/null
+++ b/test.c
@@ -0,0 +1,736 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#include <stdlib.h>
+
+
+#define MEM(S) S, sizeof(S) - 1
+
+
+#define assert(TRUTH) assert_(TRUTH, #TRUTH, __LINE__)
+#define assert_streq(RESULT, EXPECT) assert_streq_(RESULT, EXPECT, #RESULT, __LINE__)
+#define assert_zueq(RESULT, EXPECT) assert_zueq_(RESULT, EXPECT, #RESULT, __LINE__)
+
+static int from_lineno = 0;
+
+
+static void *
+allocate(size_t num, size_t size, size_t alignment, struct libar2_context *ctx)
+{
+ void *ptr;
+ int err;
+ (void) ctx;
+ if (num > SIZE_MAX / size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (alignment < sizeof(void *))
+ alignment = sizeof(void *);
+ err = posix_memalign(&ptr, alignment, num * size);
+ if (err) {
+ errno = err;
+ return NULL;
+ } else {
+ return ptr;
+ }
+}
+
+static void
+deallocate(void *ptr, struct libar2_context *ctx)
+{
+ (void) ctx;
+ free(ptr);
+}
+
+static int
+st_init_thread_pool(size_t desired, size_t *createdp, struct libar2_context *ctx)
+{
+ (void) desired;
+ (void) ctx;
+ *createdp = 0;
+ return 0;
+}
+
+static struct libar2_context ctx_st = {
+ .user_data = NULL,
+ .autoerase_message = 1,
+ .autoerase_secret = 1,
+ .autoerase_salt = 1,
+ .autoerase_associated_data = 1,
+ .allocate = allocate,
+ .deallocate = deallocate,
+ .init_thread_pool = st_init_thread_pool,
+ .get_ready_threads = NULL,
+ .run_thread = NULL,
+ .join_thread_pool = NULL,
+ .destroy_thread_pool = NULL
+};
+
+
+static int
+nulstrcmp(const char *a, const char *b)
+{
+ return !a ? -!!b : !b ? +1 : strcmp(a, b);
+}
+
+
+static void
+assert_(int truth, const char *truthstr, int lineno)
+{
+ if (!truth) {
+ if (from_lineno)
+ fprintf(stderr, "Assertion at line %i, from line %i failed: %s\n", lineno, from_lineno, truthstr);
+ else
+ fprintf(stderr, "Assertion at line %i failed: %s\n", lineno, truthstr);
+ fprintf(stderr, "\terrno: %i (%s)\n", errno, strerror(errno));
+ exit(1);
+ }
+}
+
+
+static void
+assert_streq_(const char *result, const char *expect, const char *code, int lineno)
+{
+ if (nulstrcmp(result, expect)) {
+ if (from_lineno)
+ fprintf(stderr, "Assertion at line %i, form line %i failed:\n", lineno, from_lineno);
+ else
+ fprintf(stderr, "Assertion at line %i failed:\n", lineno);
+ fprintf(stderr, "\tcode: %s\n", code);
+ fprintf(stderr, "\tresult: %s\n", result);
+ fprintf(stderr, "\texpected: %s\n", expect);
+ fprintf(stderr, "\terrno: %i (%s)\n", errno, strerror(errno));
+ exit(1);
+ }
+}
+
+
+static void
+assert_zueq_(size_t result, size_t expect, const char *code, int lineno)
+{
+ if (result != expect) {
+ if (from_lineno)
+ fprintf(stderr, "Assertion at line %i, form line %i failed:\n", lineno, from_lineno);
+ else
+ fprintf(stderr, "Assertion at line %i failed:\n", lineno);
+ fprintf(stderr, "\tcode: %s\n", code);
+ fprintf(stderr, "\tresult: %zu\n", result);
+ fprintf(stderr, "\texpected: %zu\n", expect);
+ fprintf(stderr, "\terrno: %i (%s)\n", errno, strerror(errno));
+ exit(1);
+ }
+}
+
+
+static void
+check_libar2_type_to_string(void)
+{
+ errno = 0;
+
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2D, LIBAR2_LOWER_CASE), "argon2d");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2D, LIBAR2_TITLE_CASE), "Argon2d");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2D, LIBAR2_UPPER_CASE), "ARGON2D");
+ assert(errno == 0);
+
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2I, LIBAR2_LOWER_CASE), "argon2i");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2I, LIBAR2_TITLE_CASE), "Argon2i");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2I, LIBAR2_UPPER_CASE), "ARGON2I");
+ assert(errno == 0);
+
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2ID, LIBAR2_LOWER_CASE), "argon2id");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2ID, LIBAR2_TITLE_CASE), "Argon2id");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2ID, LIBAR2_UPPER_CASE), "ARGON2ID");
+ assert(errno == 0);
+
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2DS, LIBAR2_LOWER_CASE), "argon2ds");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2DS, LIBAR2_TITLE_CASE), "Argon2ds");
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2DS, LIBAR2_UPPER_CASE), "ARGON2DS");
+ assert(errno == 0);
+
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2I, -1), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(LIBAR2_ARGON2I, 3), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_type_to_string(3, LIBAR2_LOWER_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(3, LIBAR2_TITLE_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(3, LIBAR2_UPPER_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_type_to_string(-1, LIBAR2_LOWER_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(-1, LIBAR2_TITLE_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(-1, LIBAR2_UPPER_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_type_to_string(5, LIBAR2_LOWER_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(5, LIBAR2_TITLE_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert_streq(libar2_type_to_string(5, LIBAR2_UPPER_CASE), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+}
+
+
+static void
+check_libar2_string_to_type(void)
+{
+ enum libar2_argon2_type type;
+
+ errno = 0;
+
+ assert(!libar2_string_to_type("argon2i", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("Argon2i", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("ARgon2i", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("ARGon2i", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("ARGOn2i", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("ARGON2i", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("ARGON2I", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("aRGON2I", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("arGON2I", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("argON2I", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("argoN2I", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("argon2I", &type) && type == LIBAR2_ARGON2I);
+ assert(!libar2_string_to_type("argon2i$x", &type) && type == LIBAR2_ARGON2I);
+ assert(errno == 0);
+
+ assert(!libar2_string_to_type("argon2d", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("Argon2d", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("ARgon2d", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("ARGon2d", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("ARGOn2d", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("ARGON2d", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("ARGON2D", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("aRGON2D", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("arGON2D", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("argON2D", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("argoN2D", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("argon2D", &type) && type == LIBAR2_ARGON2D);
+ assert(!libar2_string_to_type("argon2d$x", &type) && type == LIBAR2_ARGON2D);
+ assert(errno == 0);
+
+ assert(!libar2_string_to_type("argon2id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("Argon2id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("ARgon2id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("ARGon2id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("ARGOn2id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("ARGON2id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("ARGON2Id", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("ARGON2ID", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("aRGON2ID", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("arGON2ID", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("argON2ID", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("argoN2ID", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("argon2ID", &type) && type == LIBAR2_ARGON2ID);
+ assert(!libar2_string_to_type("argon2id$x", &type) && type == LIBAR2_ARGON2ID);
+ assert(errno == 0);
+
+ assert(!libar2_string_to_type("argon2ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("Argon2ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("ARgon2ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("ARGon2ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("ARGOn2ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("ARGON2ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("ARGON2Ds", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("ARGON2DS", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("aRGON2DS", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("arGON2DS", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("argON2DS", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("argoN2DS", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("argon2DS", &type) && type == LIBAR2_ARGON2DS);
+ assert(!libar2_string_to_type("argon2ds$x", &type) && type == LIBAR2_ARGON2DS);
+ assert(errno == 0);
+
+ assert(libar2_string_to_type("argon2", &type) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert(libar2_string_to_type("argon2x", &type) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+ assert(libar2_string_to_type("ARGON2", &type) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+}
+
+
+static void
+check_libar2_version_to_string(void)
+{
+ errno = 0;
+
+ assert_streq(libar2_version_to_string(LIBAR2_ARGON2_VERSION_10), "10");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string(LIBAR2_ARGON2_VERSION_13), "13");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string(0x10), "10");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string(0x13), "13");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string(0x11), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_version_to_string(0x12), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_version_to_string(0), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+}
+
+
+static void
+check_libar2_version_to_string_proper(void)
+{
+ errno = 0;
+
+ assert_streq(libar2_version_to_string_proper(LIBAR2_ARGON2_VERSION_10), "1.0");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string_proper(LIBAR2_ARGON2_VERSION_13), "1.3");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string_proper(0x10), "1.0");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string_proper(0x13), "1.3");
+ assert(errno == 0);
+
+ assert_streq(libar2_version_to_string_proper(0x11), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_version_to_string_proper(0x12), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert_streq(libar2_version_to_string_proper(0), NULL);
+ assert(errno == EINVAL);
+ errno = 0;
+}
+
+
+static void
+check_libar2_string_to_version(void)
+{
+ enum libar2_argon2_version version;
+
+ errno = 0;
+
+ assert(!libar2_string_to_version("10", &version) && version == LIBAR2_ARGON2_VERSION_10);
+ assert(errno == 0);
+
+ assert(!libar2_string_to_version("13", &version) && version == LIBAR2_ARGON2_VERSION_13);
+ assert(errno == 0);
+
+ assert(!libar2_string_to_version("1.0", &version) && version == LIBAR2_ARGON2_VERSION_10);
+ assert(errno == 0);
+
+ assert(!libar2_string_to_version("1.3", &version) && version == LIBAR2_ARGON2_VERSION_13);
+ assert(errno == 0);
+
+ assert(libar2_string_to_version("11", &version) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert(libar2_string_to_version("12", &version) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert(libar2_string_to_version("1.1", &version) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert(libar2_string_to_version("1.2", &version) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert(libar2_string_to_version("16", &version) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+
+ assert(libar2_string_to_version("19", &version) == -1);
+ assert(errno == EINVAL);
+ errno = 0;
+}
+
+
+static void
+check_libar2_encode_base64(void)
+{
+ char buf[128];
+
+ errno = 0;
+
+ assert(libar2_encode_base64(NULL, MEM("")) == 1);
+ assert(libar2_encode_base64(NULL, MEM("1")) == 3);
+ assert(libar2_encode_base64(NULL, MEM("12")) == 4);
+ assert(libar2_encode_base64(NULL, MEM("123")) == 5);
+ assert(libar2_encode_base64(NULL, MEM("1234")) == 7);
+ assert(libar2_encode_base64(NULL, MEM("12345")) == 8);
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("")) == 1);
+ assert_streq(buf, "");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("\x00")) == 3);
+ assert_streq(buf, "AA");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("\x00\x00")) == 4);
+ assert_streq(buf, "AAA");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("\x00\x00\x00")) == 5);
+ assert_streq(buf, "AAAA");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("12345678")) == 12);
+ assert_streq(buf, "MTIzNDU2Nzg");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("testtest")) == 12);
+ assert_streq(buf, "dGVzdHRlc3Q");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("zy[]y21 !")) == 13);
+ assert_streq(buf, "enlbXXkyMSAh");
+ assert(errno == 0);
+
+ assert(libar2_encode_base64(buf, MEM("{~|~}~~~\x7f\x7f")) == 15);
+ assert_streq(buf, "e358fn1+fn5/fw");
+ assert(errno == 0);
+}
+
+
+static void
+check_libar2_decode_base64(void)
+{
+ char buf[128];
+ size_t len;
+
+ errno = 0;
+
+ assert(libar2_decode_base64("", buf, &len) == 0);
+ assert(len == 0);
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("A", buf, &len) == 0);
+ assert(len == 0);
+ assert(errno == 0);
+
+#define CHECK(S) len == sizeof(S) - 1 && !memcmp(buf, S, len)
+
+ assert(libar2_decode_base64("AA", buf, &len) == 2);
+ assert(CHECK("\x00"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("AAA", buf, &len) == 3);
+ assert(CHECK("\x00\x00"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("AAAA", buf, &len) == 4);
+ assert(CHECK("\x00\x00\x00"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("AAAAA", buf, &len) == 4);
+ assert(CHECK("\x00\x00\x00"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("AAAAAA", buf, &len) == 6);
+ assert(CHECK("\x00\x00\x00\x00"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("MTIzNDU2Nzg", buf, &len) == 11);
+ assert(CHECK("12345678"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("dGVzdHRlc3Q", buf, &len) == 11);
+ assert(CHECK("testtest"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("enlbXXkyMSAh", buf, &len) == 12);
+ assert(CHECK("zy[]y21 !"));
+ assert(errno == 0);
+
+ assert(libar2_decode_base64("e358fn1+fn5/fw", buf, &len) == 14);
+ assert(CHECK("{~|~}~~~\x7f\x7f"));
+ assert(errno == 0);
+
+#undef CHECK
+}
+
+
+static void
+check_libar2_encode_params_libar2_decode_params(void)
+{
+ struct libar2_argon2_parameters params;
+ char *sbuf = NULL;
+ char pbuf[256];
+
+#define DECODE(PARAMS, HASH)\
+ libar2_decode_params(PARAMS""HASH, &params, &sbuf, &ctx_st)
+
+#define PARAMSTR "$argon2i$v=19$m=4096,t=3,p=1$fn5/f35+f38$"
+ memset(&params, 0xFF, sizeof(params));
+ assert_zueq(DECODE(PARAMSTR, "1234"), sizeof(PARAMSTR) - 1);
+ assert(params.type == LIBAR2_ARGON2I);
+ assert(params.version == LIBAR2_ARGON2_VERSION_13);
+ assert(params.t_cost == 3);
+ assert(params.m_cost == 4096);
+ assert(params.lanes == 1);
+ assert(params.salt != NULL);
+ assert(params.saltlen == 8);
+ assert(!memcmp(params.salt, "~~\x7f\x7f~~\x7f\x7f", params.saltlen));
+ assert(!params.key);
+ assert(!params.keylen);
+ assert(!params.ad);
+ assert(!params.adlen);
+ assert(params.hashlen == 3);
+ assert_zueq(libar2_encode_params(NULL, &params), sizeof(PARAMSTR));
+ assert_zueq(libar2_encode_params(pbuf, &params), sizeof(PARAMSTR));
+ assert_streq(pbuf, PARAMSTR);
+ assert(sbuf != NULL);
+ ctx_st.deallocate(sbuf, &ctx_st);
+ sbuf = NULL;
+#undef PARAMSTR
+
+#undef DECODE
+}
+
+
+static void
+check_libar2_validate_params(void)
+{
+ struct libar2_argon2_parameters params;
+ const char *errmsg = NULL;
+
+ errno = 0;
+
+ memset(&params, 0, sizeof(params));
+ params.type = LIBAR2_ARGON2I;
+ params.version = LIBAR2_ARGON2_VERSION_13;
+ params.t_cost = 3;
+ params.m_cost = 4096;
+ params.lanes = 1;
+ params.saltlen = 8;
+ params.hashlen = 4;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_OK);
+ assert_streq(errmsg, "OK");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, &errmsg) == 0);
+ assert_streq(errmsg, "OK");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == 0);
+ assert(errno == 0);
+
+ params.version = LIBAR2_ARGON2_VERSION_10;
+ assert(libar2_validate_params(&params, NULL) == 0);
+ assert(errno == 0);
+ params.type = LIBAR2_ARGON2I;
+ assert(libar2_validate_params(&params, NULL) == 0);
+ assert(errno == 0);
+ params.type = LIBAR2_ARGON2D;
+ assert(libar2_validate_params(&params, NULL) == 0);
+ assert(errno == 0);
+ params.type = LIBAR2_ARGON2DS;
+ assert(libar2_validate_params(&params, NULL) == 0);
+ assert(errno == 0);
+
+ params.hashlen = 3;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_HASH_TOO_SMALL);
+ assert_streq(errmsg, "tag length parameter is too small");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_HASH_TOO_SMALL);
+ assert(errno == 0);
+ params.hashlen = 4;
+
+ params.saltlen = 7;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_SALT_TOO_SMALL);
+ assert_streq(errmsg, "salt parameter is too small");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_SALT_TOO_SMALL);
+ assert(errno == 0);
+ params.saltlen = 8;
+
+ params.t_cost = 0;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_T_COST_TOO_SMALL);
+ assert_streq(errmsg, "time-cost parameter is too small");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_T_COST_TOO_SMALL);
+ assert(errno == 0);
+ params.t_cost = 1;
+
+ params.m_cost = 7;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_M_COST_TOO_SMALL);
+ assert_streq(errmsg, "memory-cost parameter is too small");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_M_COST_TOO_SMALL);
+ assert(errno == 0);
+ params.m_cost = 8;
+
+ params.lanes = 0;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_TOO_FEW_LANES);
+ assert_streq(errmsg, "lane-count parameter is too small");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_TOO_FEW_LANES);
+ assert(errno == 0);
+ params.lanes = 1;
+
+ params.lanes = 0x1000000UL;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_TOO_MANY_LANES);
+ assert_streq(errmsg, "lane-count parameter is too large");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_TOO_MANY_LANES);
+ assert(errno == 0);
+ params.lanes = 1;
+
+ params.type = -1;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_INVALID_TYPE);
+ assert_streq(errmsg, "type parameter is invalid");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_INVALID_TYPE);
+ assert(errno == 0);
+ params.type = 0;
+
+ params.type = 3;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_INVALID_TYPE);
+ assert_streq(errmsg, "type parameter is invalid");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_INVALID_TYPE);
+ assert(errno == 0);
+ params.type = 0;
+
+ params.type = 5;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_INVALID_TYPE);
+ assert_streq(errmsg, "type parameter is invalid");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_INVALID_TYPE);
+ assert(errno == 0);
+ params.type = 0;
+
+ params.version = 0x11;
+ assert(libar2_validate_params(&params, &errmsg) == LIBAR2_INVALID_VERSION);
+ assert_streq(errmsg, "version parameter is invalid");
+ assert(errno == 0);
+ errmsg = NULL;
+ assert(libar2_validate_params(&params, NULL) == LIBAR2_INVALID_VERSION);
+ assert(errno == 0);
+ params.type = 0x10;
+}
+
+
+static void
+check_hash(const char *pwd_, size_t pwdlen, const char *hash, struct libar2_context *ctx, int lineno)
+{
+ struct libar2_argon2_parameters params;
+ char *sbuf, output[512], pwd[512], output64[700];
+ size_t plen;
+
+ from_lineno = lineno;
+ errno = 0;
+
+ stpcpy(pwd, pwd_);
+ plen = libar2_decode_params(hash, &params, &sbuf, ctx);
+ assert(!libar2_validate_params(&params, NULL));
+ assert(!libar2_hash(output, pwd, pwdlen, &params, ctx));
+ libar2_encode_base64(output64, output, params.hashlen);
+ assert_streq(output64, &hash[plen]);
+ assert(errno == 0);
+ if (sbuf) {
+ ctx->deallocate(sbuf, ctx);
+ }
+
+ from_lineno = 0;
+}
+
+
+static void
+check_libar2_hash(void)
+{
+#define CHECK(PWD, HASH)\
+ check_hash(MEM(PWD), HASH, &ctx_st, __LINE__);
+
+ CHECK("\x00", "$argon2d$v=16$m=8,t=1,p=1$ICAgICAgICA$Eyx1BxGazSuPQoy7osaQuo20Dw9VI97dYUOgcC3cMgw");
+ CHECK("test", "$argon2i$v=19$m=4096,t=3,p=1$fn5/f35+f38$9tqKA4WMEsSAOEUwatjxvJLSqL1j0GQkgbsfnpresDw");
+ CHECK("\x00", "$argon2id$v=16$m=8,t=1,p=1$ICAgICAgICA$fXq1aUbp9yhbn+EQc4AzUUE6AKnHAkvzIXsN6J4ukvE");
+ CHECK("", "$argon2d$v=16$m=8,t=1,p=1$ICAgICAgICA$X54KZYxUSfMUihzebb70sKbheabHilo8gsUldrVU4IU");
+ CHECK("", "$argon2d$v=16$m=8,t=1,p=1$ICAgICAgICA$NjODMrWrS7zeivNNpHsuxD9c6uDmUQ6YqPRhb8H5DSNw9n683FUCJZ3tyxgfJpYYANI+01WT/S5zp1UVs+qNRwnkdEyLKZMg+DIOXVc9z1po9ZlZG8+Gp4g5brqfza3lvkR9vw");
+ CHECK("", "$argon2ds$v=16$m=8,t=1,p=1$ICAgICAgICA$zgdykk9ZjN5VyrW0LxGw8LmrJ1Z6fqSC+3jPQtn4n0s");
+
+ CHECK("password", "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ");
+ CHECK("password", "$argon2i$m=1048576,t=2,p=1$c29tZXNhbHQ$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk");
+ CHECK("password", "$argon2i$m=262144,t=2,p=1$c29tZXNhbHQ$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc");
+ CHECK("password", "$argon2i$m=256,t=2,p=1$c29tZXNhbHQ$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY");
+ CHECK("password", "$argon2i$m=256,t=2,p=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs");
+ CHECK("password", "$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI");
+ CHECK("password", "$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs");
+ CHECK("differentpassword", "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM");
+ CHECK("password", "$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc");
+
+ CHECK("password", "$argon2i$v=19$m=1048576,t=2,p=1$c29tZXNhbHQ$0Vh6ygkiw7XWqD7asxvuPE667zQu1hJ6VdGbI1GtH0E");
+ CHECK("password", "$argon2i$v=19$m=262144,t=2,p=1$c29tZXNhbHQ$KW266AuAfNzqrUSudBtQbxTbCVkmexg7EY+bJCKbx8s");
+ CHECK("password", "$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8");
+ CHECK("password", "$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E");
+ CHECK("password", "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8");
+ CHECK("password", "$argon2i$v=19$m=65536,t=4,p=1$c29tZXNhbHQ$qqlT1YrzcGzj3xrv1KZKhOMdf1QXUjHxKFJZ+IF0zls");
+ CHECK("differentpassword", "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4");
+ CHECK("password", "$argon2i$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ$sDV8zPvvkfOGCw26RHsjSMvv7K2vmQq/6cxAcmxSEnE");
+
+ CHECK("password", "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc");
+ CHECK("password", "$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow");
+ CHECK("password", "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4");
+ CHECK("password", "$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc");
+ CHECK("password", "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg");
+ CHECK("password", "$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw");
+ CHECK("differentpassword", "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94");
+ CHECK("password", "$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw");
+
+#undef CHECK
+}
+
+
+int
+main(void)
+{
+ check_libar2_type_to_string();
+ check_libar2_string_to_type();
+ check_libar2_version_to_string();
+ check_libar2_version_to_string_proper();
+ check_libar2_string_to_version();
+ check_libar2_encode_base64();
+ check_libar2_decode_base64();
+ check_libar2_encode_params_libar2_decode_params();
+ check_libar2_validate_params();
+ check_libar2_hash();
+}