From: Howard Chu Date: Thu, 2 Jul 2015 16:05:14 +0000 (+0100) Subject: Add TOTP pw mechanism X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=9a3506a9f9f3aad7627a16f3d667d6c459b86e46;p=openldap Add TOTP pw mechanism --- diff --git a/contrib/slapd-modules/passwd/totp/Makefile b/contrib/slapd-modules/passwd/totp/Makefile new file mode 100644 index 0000000000..e25d3164ff --- /dev/null +++ b/contrib/slapd-modules/passwd/totp/Makefile @@ -0,0 +1,46 @@ +# $OpenLDAP$ + +LDAP_SRC = ../../../.. +LDAP_BUILD = $(LDAP_SRC) +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 = +INCS = $(LDAP_INC) +LIBS = $(LDAP_LIB) + +PROGRAMS = pw-totp.la +LTVER = 0:0:0 + +prefix=/usr/local +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-totp.la: slapd-totp.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/totp/README b/contrib/slapd-modules/passwd/totp/README new file mode 100644 index 0000000000..df84f35ba6 --- /dev/null +++ b/contrib/slapd-modules/passwd/totp/README @@ -0,0 +1,75 @@ +TOTP OpenLDAP support +---------------------- + +slapd-totp.c provides support for RFC 6238 TOTP Time-based One +Time Passwords in OpenLDAP using SHA-1, SHA-256, and SHA-512. +For instance, one could have the LDAP attribute: + +userPassword: {TOTP1}GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ + +which encodes the key '12345678901234567890'. + + +Building +-------- + +1) Customize the LDAP_SRC variable in Makefile to point to the OpenLDAP +source root. + +2) Run 'make' to produce slapd-totp.so + +3) Copy slapd-totp.so somewhere permanent. + +4) Edit your slapd.conf (eg. /etc/ldap/slapd.conf), and add: + +moduleload ...path/to/slapd-sha2.so + +5) This module also requires the use of the slapo-lastbind overlay. You +should build that module and also add it to your slapd configuration. + +6) Restart slapd. + + +Configuring +----------- + +The {TOTP1}, {TOTP256}, and {TOTP512} password schemes should now be recognised. + +You can also tell OpenLDAP to use one of these new schemes when processing LDAP +Password Modify Extended Operations, thanks to the password-hash option in +slapd.conf. For example: + +password-hash {TOTP1} + +On the databases where your users reside you must configure both the +lastbind overlay and the totp overlay: + +database mdb +... + +overlay lastbind +overlay totp + + + +Testing +------- + +The TOTP1 algorithm is compatible with Google Authenticator. + +--- + +This work is part of OpenLDAP Software . + +Copyright 2015 The OpenLDAP Foundation. +Portions Copyright 2015 by Howard Chu, Symas Corp. +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 +. + diff --git a/contrib/slapd-modules/passwd/totp/slapd-totp.c b/contrib/slapd-modules/passwd/totp/slapd-totp.c new file mode 100644 index 0000000000..ec8fae61f8 --- /dev/null +++ b/contrib/slapd-modules/passwd/totp/slapd-totp.c @@ -0,0 +1,580 @@ +/* slapd-totp.c - Password module and overlay for TOTP */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2015 The OpenLDAP Foundation. + * Portions Copyright 2015 by Howard Chu, Symas Corp. + * 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 + * . + */ + +#include + +#include +#include +#include "lutil.h" +#include +#include +#include +/* include socket.h to get sys/types.h and/or winsock2.h */ +#include + +#include +#include + +#include "slap.h" +#include "config.h" + +static LUTIL_PASSWD_CHK_FUNC chk_totp1, chk_totp256, chk_totp512; +static LUTIL_PASSWD_HASH_FUNC hash_totp1, hash_totp256, hash_totp512; +static const struct berval scheme_totp1 = BER_BVC("{TOTP1}"); +static const struct berval scheme_totp256 = BER_BVC("{TOTP256}"); +static const struct berval scheme_totp512 = BER_BVC("{TOTP512}"); + +static AttributeDescription *ad_authTimestamp; + +/* RFC3548 base32 encoding/decoding */ + +static const char Base32[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +static const char Pad32 = '='; + +static int +totp_b32_ntop( + u_char const *src, + size_t srclength, + char *target, + size_t targsize) +{ + size_t datalength = 0; + u_char input0; + u_int input1; /* assumed to be at least 32 bits */ + u_char output[8]; + int i; + + while (4 < srclength) { + if (datalength + 8 > targsize) + return (-1); + input0 = *src++; + input1 = *src++; + input1 <<= 8; + input1 |= *src++; + input1 <<= 8; + input1 |= *src++; + input1 <<= 8; + input1 |= *src++; + srclength -= 5; + + for (i=7; i>1; i--) { + output[i] = input1 & 0x1f; + input1 >>= 5; + } + output[0] = input0 >> 3; + output[1] = (input0 & 0x07) << 2 | input1; + + for (i=0; i<8; i++) + target[datalength++] = Base32[output[i]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + static const int outlen[] = { 2,4,5,7 }; + int n; + if (datalength + 8 > targsize) + return (-1); + + /* Get what's left. */ + input1 = *src++; + for (i = 1; i < srclength; i++) { + input1 <<= 8; + input1 |= *src++; + } + input1 <<= 8 * (4-srclength); + n = outlen[srclength-1]; + for (i=0; i> 27]; + input1 <<= 5; + } + for (; i<8; i++) + target[datalength++] = Pad32; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* converts characters, eight at a time, starting at src + from base - 32 numbers into five 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +static int +totp_b32_pton( + char const *src, + u_char *target, + size_t targsize) +{ + int tarindex, state, ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (ch == Pad32) + break; + + pos = strchr(Base32, ch); + if (pos == 0) /* A non-base32 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base32) << 3; + } + state = 1; + break; + case 1: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base32) >> 2; + target[tarindex+1] = ((pos - Base32) & 0x3) + << 6 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + target[tarindex] |= (pos - Base32) << 1; + } + state = 3; + break; + case 3: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base32) >> 4; + target[tarindex+1] = ((pos - Base32) & 0xf) + << 4 ; + } + tarindex++; + state = 4; + break; + case 4: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base32) >> 1; + target[tarindex+1] = ((pos - Base32) & 0x1) + << 7 ; + } + tarindex++; + state = 5; + break; + case 5: + if (target) { + target[tarindex] |= (pos - Base32) << 2; + } + state = 6; + break; + case 6: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base32) >> 3; + target[tarindex+1] = ((pos - Base32) & 0x7) + << 5 ; + } + tarindex++; + state = 7; + break; + case 7: + if (target) { + target[tarindex] |= (pos - Base32); + } + state = 0; + tarindex++; + break; + + default: + abort(); + } + } + + /* + * We are done decoding Base-32 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad32) { /* We got a pad char. */ + int i = 1; + + /* count pad chars */ + for (; ch; ch = *src++) { + if (ch != Pad32) + return (-1); + i++; + } + /* there are only 4 valid ending states with a + * pad character, make sure the number of pads is valid. + */ + switch(state) { + case 2: if (i != 6) return -1; + break; + case 4: if (i != 4) return -1; + break; + case 5: if (i != 3) return -1; + break; + case 7: if (i != 1) return -1; + break; + default: + return -1; + } + /* + * Now make sure that the "extra" bits that slopped past + * the last full byte were zeros. If we don't check them, + * they become a subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} + +/* RFC6238 TOTP */ + +#define HMAC_setup(ctx, key, len, hash) HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, key, len, hash, 0) +#define HMAC_crunch(ctx, buf, len) HMAC_Update(&ctx, buf, len) +#define HMAC_finish(ctx, dig, dlen) HMAC_Final(&ctx, dig, &dlen); HMAC_CTX_cleanup(&ctx) + +typedef struct myval { + ber_len_t mv_len; + void *mv_val; +} myval; + +static void do_hmac( + const void *hash, + myval *key, + myval *data, + myval *out) +{ + HMAC_CTX ctx; + unsigned int digestLen; + + HMAC_setup(ctx, key->mv_val, key->mv_len, hash); + HMAC_crunch(ctx, data->mv_val, data->mv_len); + HMAC_finish(ctx, out->mv_val, digestLen); + out->mv_len = digestLen; +} + +static const int DIGITS_POWER[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + +static void generate( + myval *key, + unsigned long tval, + int digits, + myval *out, + const void *mech) +{ + unsigned char digest[SHA512_DIGEST_LENGTH]; + myval digval; + myval data; + unsigned char msg[8]; + int i, offset, res, otp; + +#if !WORDS_BIGENDIAN + /* only needed on little-endian, can just use tval directly on big-endian */ + for (i=7; i>=0; i--) { + msg[i] = tval & 0xff; + tval >>= 8; + } +#endif + + data.mv_val = msg; + data.mv_len = sizeof(msg); + + digval.mv_val = digest; + digval.mv_len = sizeof(digest); + do_hmac(mech, key, &data, &digval); + + offset = digest[digval.mv_len-1] & 0xf; + res = ((digest[offset] & 0x7f) << 24) | + ((digest[offset+1] & 0xff) << 16) | + ((digest[offset+2] & 0xff) << 8) | + (digest[offset+3] & 0xff); + + otp = res % DIGITS_POWER[digits]; + out->mv_len = snprintf(out->mv_val, out->mv_len, "%0*d", digits, otp); +} + +static int totp_op_cleanup( Operation *op, SlapReply *rs ); + +#define TIME_STEP 30 +#define DIGITS 6 + +static int chk_totp( + const struct berval *passwd, + const struct berval *cred, + const void *mech, + const char **text) +{ + void *ctx, *op_tmp; + Operation *op; + Entry *e; + Attribute *a; + long t = time(0L) / TIME_STEP; + int rc; + myval out, key; + char outbuf[32]; + + /* Find our thread context, find our Operation */ + ctx = ldap_pvt_thread_pool_context(); + if (ldap_pvt_thread_pool_getkey(ctx, totp_op_cleanup, &op_tmp, NULL) || + !op_tmp) + return LUTIL_PASSWD_ERR; + op = op_tmp; + + rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e); + if (rc != LDAP_SUCCESS) return LUTIL_PASSWD_ERR; + + /* Make sure previous login is older than current time */ + a = attr_find(e->e_attrs, ad_authTimestamp); + if (a) { + struct lutil_tm tm; + struct lutil_timet tt; + if (lutil_parsetime(a->a_vals[0].bv_val, &tm) == 0 && + lutil_tm2time(&tm, &tt) == 0) { + long told = tt.tt_sec / TIME_STEP; + if (told >= t) + rc = LUTIL_PASSWD_ERR; + } + } /* else no previous login, 1st use is OK */ + + be_entry_release_r(op, e); + if (rc) return rc; + + /* Key is stored in base32 */ + key.mv_len = passwd->bv_len * 5 / 8; + key.mv_val = ber_memalloc(key.mv_len+1); + + if (!key.mv_val) + return LUTIL_PASSWD_ERR; + + rc = totp_b32_pton(passwd->bv_val, key.mv_val, key.mv_len); + if (rc < 1) { + rc = LUTIL_PASSWD_ERR; + goto out; + } + + out.mv_val = outbuf; + out.mv_len = sizeof(outbuf); + generate(&key, t, DIGITS, &out, mech); + memset(key.mv_val, 0, key.mv_len); + + /* compare */ + if (out.mv_len != cred->bv_len) + return LUTIL_PASSWD_ERR; + + rc = memcmp(out.mv_val, cred->bv_val, out.mv_len) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK; + +out: + ber_memfree(key.mv_val); + return rc; +} + +static int chk_totp1( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) +{ + return chk_totp(passwd, cred, EVP_sha1(), text); +} + +static int chk_totp256( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) +{ + return chk_totp(passwd, cred, EVP_sha256(), text); +} + +static int chk_totp512( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) +{ + return chk_totp(passwd, cred, EVP_sha512(), text); +} + +static int passwd_string32( + const struct berval *scheme, + const struct berval *passwd, + struct berval *hash) +{ + int b32len = (passwd->bv_len + 4)/5 * 8; + int rc; + hash->bv_len = scheme->bv_len + b32len; + hash->bv_val = ber_memalloc(hash->bv_len + 1); + AC_MEMCPY(hash->bv_val, scheme->bv_val, scheme->bv_len); + rc = totp_b32_ntop((unsigned char *)passwd->bv_val, passwd->bv_len, + hash->bv_val + scheme->bv_len, b32len+1); + if (rc < 0) { + ber_memfree(hash->bv_val); + hash->bv_val = NULL; + return LUTIL_PASSWD_ERR; + } + return LUTIL_PASSWD_OK; +} + +static int hash_totp1( + const struct berval *scheme, + const struct berval *passwd, + struct berval *hash, + const char **text) +{ +#if 0 + if (passwd->bv_len != SHA_DIGEST_LENGTH) { + *text = "invalid key length"; + return LUTIL_PASSWD_ERR; + } +#endif + return passwd_string32(scheme, passwd, hash); +} + +static int hash_totp256( + const struct berval *scheme, + const struct berval *passwd, + struct berval *hash, + const char **text) +{ +#if 0 + if (passwd->bv_len != SHA256_DIGEST_LENGTH) { + *text = "invalid key length"; + return LUTIL_PASSWD_ERR; + } +#endif + return passwd_string32(scheme, passwd, hash); +} + +static int hash_totp512( + const struct berval *scheme, + const struct berval *passwd, + struct berval *hash, + const char **text) +{ +#if 0 + if (passwd->bv_len != SHA512_DIGEST_LENGTH) { + *text = "invalid key length"; + return LUTIL_PASSWD_ERR; + } +#endif + return passwd_string32(scheme, passwd, hash); +} + +static int totp_op_cleanup( + Operation *op, + SlapReply *rs ) +{ + slap_callback *cb; + + /* clear out the current key */ + ldap_pvt_thread_pool_setkey( op->o_threadctx, totp_op_cleanup, + NULL, 0, NULL, NULL ); + + /* free the callback */ + cb = op->o_callback; + op->o_callback = cb->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + return 0; +} + +static int totp_op_bind( + Operation *op, + SlapReply *rs ) +{ + /* If this is a simple Bind, stash the Op pointer so our chk + * function can find it. Set a cleanup callback to clear it + * out when the Bind completes. + */ + if ( op->oq_bind.rb_method == LDAP_AUTH_SIMPLE ) { + slap_callback *cb; + ldap_pvt_thread_pool_setkey( op->o_threadctx, + totp_op_cleanup, op, 0, NULL, NULL ); + cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = totp_op_cleanup; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + return SLAP_CB_CONTINUE; +} + +static int totp_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + int rc = 0; + + if (!ad_authTimestamp) { + const char *text = NULL; + rc = slap_str2ad("authTimestamp", &ad_authTimestamp, &text); + if (rc) { + snprintf(cr->msg, sizeof(cr->msg), "unable to find authTimestamp attribute: %s (%d)", + text, rc); + Debug(LDAP_DEBUG_ANY, "totp: %s.\n", cr->msg, 0, 0); + } + } + return rc; +} + +static slap_overinst totp; + +int +totp_initialize(void) +{ + int rc; + + totp.on_bi.bi_type = "totp"; + + totp.on_bi.bi_db_open = totp_db_open; + totp.on_bi.bi_op_bind = totp_op_bind; + + rc = lutil_passwd_add((struct berval *) &scheme_totp1, chk_totp1, hash_totp1); + if (!rc) + rc = lutil_passwd_add((struct berval *) &scheme_totp256, chk_totp256, hash_totp256); + if (!rc) + rc = lutil_passwd_add((struct berval *) &scheme_totp512, chk_totp512, hash_totp512); + if (rc) + return rc; + + return overlay_register(&totp); +} + +int init_module(int argc, char *argv[]) { + return totp_initialize(); +}