From 2210a3bd8432d4d43d3ef5459ca71cefff679392 Mon Sep 17 00:00:00 2001 From: Simon Levermann <simon@slevermann.de> Date: Wed, 25 Jan 2017 15:11:06 +0100 Subject: [PATCH] ITS#8575 Implement argon2 password hashing as a module This change implements argon2, which won the Password Hashing Competition (https://password-hashing.net/) as a contrib-module in order to provide a modern password hashing alternative in openldap. The currently available password hashing algorithms are relatively old, and modern hardware, especially GPUs can compute quite a few (ranging from tens of thousands to millions) of hashes per second. Argon2 was designed to withstand such attacks. This implementation uses the default work factors used in the argon2 command line client, but the resulting hashes are stored in a way that would allow retroactive changes to these values, or even exposing them as configuration in the module. --- contrib/slapd-modules/passwd/argon2/Makefile | 48 +++++++ contrib/slapd-modules/passwd/argon2/README | 109 ++++++++++++++++ .../slapd-modules/passwd/argon2/pw-argon2.c | 117 ++++++++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 contrib/slapd-modules/passwd/argon2/Makefile create mode 100644 contrib/slapd-modules/passwd/argon2/README create mode 100644 contrib/slapd-modules/passwd/argon2/pw-argon2.c diff --git a/contrib/slapd-modules/passwd/argon2/Makefile b/contrib/slapd-modules/passwd/argon2/Makefile new file mode 100644 index 0000000000..3e7aa2dd35 --- /dev/null +++ b/contrib/slapd-modules/passwd/argon2/Makefile @@ -0,0 +1,48 @@ +# $OpenLDAP$ + +LDAP_SRC = ../../../.. +LDAP_BUILD = ../../../.. +LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd +LDAP_LIB = $(LDAP_BUILD)/libraries/libldap_r/libldap_r.la \ + $(LDAP_BUILD)/libraries/liblber/liblber.la + +LIBTOOL = $(LDAP_BUILD)/libtool +CC = gcc +OPT = -g -O2 -Wall +#DEFS = -DSLAPD_ARGON2_DEBUG + +INCS = $(LDAP_INC) +LIBS = $(LDAP_LIB) -largon2 + +PROGRAMS = pw-argon2.la +LTVER = 0:0:0 + +#prefix=/usr/local +prefix=`grep -e "^prefix =" $(LDAP_BUILD)/Makefile | cut -d= -f2` + +exec_prefix=$(prefix) +ldap_subdir=/openldap + +libdir=$(exec_prefix)/lib +libexecdir=$(exec_prefix)/libexec +moduledir = $(libexecdir)$(ldap_subdir) + +.SUFFIXES: .c .o .lo + +.c.lo: + $(LIBTOOL) --mode=compile $(CC) $(OPT) $(DEFS) $(INCS) -c $< + +all: $(PROGRAMS) + +pw-argon2.la: pw-argon2.lo + $(LIBTOOL) --mode=link $(CC) $(OPT) -version-info $(LTVER) \ + -rpath $(moduledir) -module -o $@ $? $(LIBS) + +clean: + rm -rf *.o *.lo *.la .libs + +install: $(PROGRAMS) + mkdir -p $(DESTDIR)$(moduledir) + for p in $(PROGRAMS) ; do \ + $(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \ + done diff --git a/contrib/slapd-modules/passwd/argon2/README b/contrib/slapd-modules/passwd/argon2/README new file mode 100644 index 0000000000..656df8f722 --- /dev/null +++ b/contrib/slapd-modules/passwd/argon2/README @@ -0,0 +1,109 @@ +Argon2 OpenLDAP support +---------------------- + +slapd-argon2.c provides support for ARGON2 hashed passwords in OpenLDAP. For +instance, one could have the LDAP attribute: + +userPassword: {ARGON2}$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHQ$DKlexoEJUoZTmkAAC3SaMWk30El9/RvVhlqGo6afIng + +or: + +userPassword: {ARGON2}$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0$qOCkx9nMeFlaGOO4DUmPDgrlUbgMMuO9T1+vQCFuyzw + +Both hash the password "secret", the first using the salt "saltsalt", the second using the salt "saltsaltsalt" + +Building +-------- + +1) Customize the OPENLDAP variable in Makefile to point to the OpenLDAP +source root. + +For initial testing you might also want to edit DEFS to define +SLAPD_ARGON2_DEBUG, which enables logging to stderr (don't leave this on +in production, as it prints passwords in cleartext). + +2) Run 'make' to produce slapd-argon2.so + +3) Copy slapd-argon2.so somewhere permanent. + +4) Edit your slapd.conf (eg. /etc/ldap/slapd.conf), and add: + +moduleload ...path/to/slapd-argon2.so + +5) Restart slapd. + + +Configuring +----------- + +The {ARGON2} password scheme should now be recognised. + +You can also tell OpenLDAP to use one of this scheme when processing LDAP +Password Modify Extended Operations, thanks to the password-hash option in +slapd.conf: + +password-hash {ARGON2} + + +Testing +------- + +A quick way to test whether it's working is to customize the rootdn and +rootpw in slapd.conf, eg: + +rootdn "cn=admin,dc=example,dc=com" + +# This hashes the string 'secret', with a random salt +rootpw {ARGON2}$argon2i$v=19$m=4096,t=3,p=1$uJyf0UfB25SQTfX7oCyK2w$U45DJqEFwD0yFaLvTVyACHLvGMwzNGf19dvzPR8XvGc + + +Then to test, run something like: + +ldapsearch -b "dc=example,dc=com" -D "cn=admin,dc=example,dc=com" -x -w secret + + +-- Test hashes: + +Test hashes can be generated with argon2: +$ echo -n "secret" | argon2 "saltsalt" -e +$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHQ$DKlexoEJUoZTmkAAC3SaMWk30El9/RvVhlqGo6afIng + +$ echo -n "secret" | argon2 "saltsaltsalt" -e +$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0$qOCkx9nMeFlaGOO4DUmPDgrlUbgMMuO9T1+vQCFuyzw + +$ echo -n "secretsecret" | argon2 "saltsalt" -e +$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHQ$U0Pd/wEsssZ9bHezDA8oxHnWe01xftykEy+7ehM2vic + +$ echo -n "secretsecret" | argon2 "saltsaltsalt" -e +$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0$fkvoOwKgVtlX9ZDqcHFyyArBvqnAM0Igca8SScB4Jsc + + + +Alternatively we could modify an existing user's password with +ldappasswd, and then test binding as that user: + +$ ldappasswd -D "cn=admin,dc=example,dc=com" -x -W -S uid=jturner,ou=People,dc=example,dc=com +New password: secret +Re-enter new password: secret +Enter LDAP Password: <cn=admin's password> + +$ ldapsearch -b "dc=example,dc=com" -D "uid=jturner,ou=People,dc=example,dc=com" -x -w secret + + + +--- + +This work is part of OpenLDAP Software <http://www.openldap.org/>. + +Copyright 2017 The OpenLDAP Foundation. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +A copy of this license is available in the file LICENSE in the +top-level directory of the distribution or, alternatively, at +<http://www.OpenLDAP.org/license.html>. + +--- diff --git a/contrib/slapd-modules/passwd/argon2/pw-argon2.c b/contrib/slapd-modules/passwd/argon2/pw-argon2.c new file mode 100644 index 0000000000..df0f715c1f --- /dev/null +++ b/contrib/slapd-modules/passwd/argon2/pw-argon2.c @@ -0,0 +1,117 @@ +/* pw-argon2.c - Password module for argon2 */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2017 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#define _GNU_SOURCE + +#include "portable.h" +#include "ac/string.h" +#include "lber_pvt.h" +#include "lutil.h" + +#include <argon2.h> +#include <stdint.h> +#include <stdlib.h> + +/* + * For now, we hardcode the default values from the argon2 command line tool + * (as of argon2 release 20161029) + */ +#define SLAPD_ARGON2_ITERATIONS 3 +#define SLAPD_ARGON2_MEMORY 12 +#define SLAPD_ARGON2_PARALLELISM 1 +#define SLAPD_ARGON2_SALT_LENGTH 16 +#define SLAPD_ARGON2_HASH_LENGTH 32 + +const struct berval slapd_argon2_scheme = BER_BVC("{ARGON2}"); + +static int slapd_argon2_hash( + const struct berval *scheme, + const struct berval *passwd, + struct berval *hash, + const char **text) { + + /* + * Duplicate these values here so future code which allows + * configuration has an easier time. + */ + uint32_t iterations = SLAPD_ARGON2_ITERATIONS; + uint32_t memory = (1 << SLAPD_ARGON2_MEMORY); + uint32_t parallelism = SLAPD_ARGON2_PARALLELISM; + uint32_t salt_length = SLAPD_ARGON2_SALT_LENGTH; + uint32_t hash_length = SLAPD_ARGON2_HASH_LENGTH; + + size_t encoded_length = argon2_encodedlen(iterations, memory, parallelism, + salt_length, hash_length, Argon2_i); + + /* + * Gather random bytes for our salt + */ + struct berval salt; + salt.bv_len = salt_length; + salt.bv_val = ber_memalloc(salt.bv_len); + + int rc = lutil_entropy((unsigned char*)salt.bv_val, salt.bv_len); + + if(rc) { + ber_memfree(salt.bv_val); + return LUTIL_PASSWD_ERR; + } + + struct berval encoded; + encoded.bv_len = encoded_length; + encoded.bv_val = ber_memalloc(encoded.bv_len); + /* + * Do the actual heavy lifting + */ + rc = argon2i_hash_encoded(iterations, memory, parallelism, + passwd->bv_val, passwd->bv_len, salt.bv_val, salt_length, hash_length, + encoded.bv_val, encoded_length); + ber_memfree(salt.bv_val); + + if(rc) { + ber_memfree(encoded.bv_val); + return LUTIL_PASSWD_ERR; + } + + hash->bv_len = scheme->bv_len + encoded_length; + hash->bv_val = ber_memalloc(hash->bv_len); + + AC_MEMCPY(hash->bv_val, scheme->bv_val, scheme->bv_len); + AC_MEMCPY(hash->bv_val + scheme->bv_len, encoded.bv_val, encoded.bv_len); + + ber_memfree(encoded.bv_val); + + return LUTIL_PASSWD_OK; +} + +static int slapd_argon2_verify( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) { + + int rc = argon2i_verify(passwd->bv_val, cred->bv_val, cred->bv_len); + + if (rc) { + return LUTIL_PASSWD_ERR; + } + return LUTIL_PASSWD_OK; +} + +int init_module(int argc, char *argv[]) { + return lutil_passwd_add((struct berval *)&slapd_argon2_scheme, + slapd_argon2_verify, slapd_argon2_hash); +} -- GitLab