diff -Naur totp.orig/README totp/README --- totp.orig/README 2019-09-06 13:45:45.439495913 -0400 +++ totp/README 2019-09-13 11:48:33.242313524 -0400 @@ -9,6 +9,17 @@ which encodes the key '12345678901234567890'. +It can also encode credentials consisting of a TOTP and a static +password. The format for this is: + +userPassword: {TOTP1ANDPW}GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ| + +where can be any scheme currently understood +by OpenLDAP. For example, using '{SHA}5en6G6MezRroT3XKqkdPOmY/BfQ=' +would encode the above TOTP with a static password of 'secret'. To +authenticate using this scheme, enter the static password immediately +followed by the TOTP, for example 'secret123456'. + Building -------- @@ -33,7 +44,8 @@ Configuring ----------- -The {TOTP1}, {TOTP256}, and {TOTP512} password schemes should now be recognised. +The {TOTP1}, {TOTP256}, {TOTP512}, {TOTP1ANDPW}, {TOTP256ANDPW}, +and {TOTP512ANDPW} 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 diff -Naur totp.orig/slapd-totp.c totp/slapd-totp.c --- totp.orig/slapd-totp.c 2019-09-06 13:46:01.727802519 -0400 +++ totp/slapd-totp.c 2019-09-13 11:49:26.087316785 -0400 @@ -94,11 +94,16 @@ #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 LUTIL_PASSWD_CHK_FUNC chk_totp1, chk_totp256, chk_totp512, + chk_totp1andpw, chk_totp256andpw, chk_totp512andpw; +static LUTIL_PASSWD_HASH_FUNC hash_totp1, hash_totp256, hash_totp512, + hash_totp1andpw, hash_totp256andpw, hash_totp512andpw; 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 const struct berval scheme_totp1andpw = BER_BVC("{TOTP1ANDPW}"); +static const struct berval scheme_totp256andpw = BER_BVC("{TOTP256ANDPW}"); +static const struct berval scheme_totp512andpw = BER_BVC("{TOTP512ANDPW}"); static AttributeDescription *ad_authTimestamp; @@ -412,6 +417,8 @@ #define TIME_STEP 30 #define DIGITS 6 +#define DELIM '|' /* a single character */ +#define TOTP_AND_PW_HASH_SCHEME "{SSHA}" static int chk_totp( const struct berval *passwd, @@ -423,7 +430,7 @@ Operation *op; Entry *e; Attribute *a; - long t = time(0L) / TIME_STEP; + long t, told = 0; int rc; myval out, key; char outbuf[32]; @@ -439,13 +446,14 @@ if (rc != LDAP_SUCCESS) return LUTIL_PASSWD_ERR; /* Make sure previous login is older than current time */ + t = op->o_time / TIME_STEP; 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; + told = tt.tt_sec / TIME_STEP; if (told >= t) rc = LUTIL_PASSWD_ERR; } @@ -494,6 +502,59 @@ return rc; } +static int chk_totp_and_pw( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text, + const void *mech) +{ + char *s; + int rc = LUTIL_PASSWD_ERR, rc_pass, rc_otp; + ber_len_t len; + struct berval cred_pass, cred_otp, passwd_pass, passwd_otp; + + /* Check credential length, no point to continue if too short */ + if (cred->bv_len <= DIGITS) + return rc; + + /* The OTP seed of the stored password */ + s = strchr(passwd->bv_val, DELIM); + if (s) { + len = s - passwd->bv_val; + } else { + return rc; + } + if (!ber_str2bv(passwd->bv_val, len, 1, &passwd_otp)) + return rc; + + /* The password part of the stored password */ + s++; + ber_str2bv(s, 0, 0, &passwd_pass); + + /* The OTP part of the entered credential */ + ber_str2bv(&cred->bv_val[cred->bv_len - DIGITS], DIGITS, 0, &cred_otp); + + /* The password part of the entered credential */ + if (!ber_str2bv(cred->bv_val, cred->bv_len - DIGITS, 0, &cred_pass)) { + /* Cleanup */ + memset(passwd_otp.bv_val, 0, passwd_otp.bv_len); + ber_memfree(passwd_otp.bv_val); + return rc; + } + + rc_otp = chk_totp(&passwd_otp, &cred_otp, mech, text); + rc_pass = lutil_passwd(&passwd_pass, &cred_pass, NULL, text); + if (rc_otp == LUTIL_PASSWD_OK && rc_pass == LUTIL_PASSWD_OK) + rc = LUTIL_PASSWD_OK; + + /* Cleanup and return */ + memset(passwd_otp.bv_val, 0, passwd_otp.bv_len); + ber_memfree(passwd_otp.bv_val); + + return rc; +} + static int chk_totp1( const struct berval *scheme, const struct berval *passwd, @@ -521,6 +582,33 @@ return chk_totp(passwd, cred, TOTP_SHA512, text); } +static int chk_totp1andpw( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) +{ + return chk_totp_and_pw(scheme, passwd, cred, text, TOTP_SHA1); +} + +static int chk_totp256andpw( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) +{ + return chk_totp_and_pw(scheme, passwd, cred, text, TOTP_SHA256); +} + +static int chk_totp512andpw( + const struct berval *scheme, + const struct berval *passwd, + const struct berval *cred, + const char **text) +{ + return chk_totp_and_pw(scheme, passwd, cred, text, TOTP_SHA512); +} + static int passwd_string32( const struct berval *scheme, const struct berval *passwd, @@ -541,6 +629,74 @@ return LUTIL_PASSWD_OK; } +static int hash_totp_and_pw( + const struct berval *scheme, + const struct berval *passwd, + struct berval *hash, + const char **text) +{ + struct berval otp, pass, hash_otp, hash_pass; + ber_len_t len; + char *s; + int rc = LUTIL_PASSWD_ERR; + + /* The OTP seed part */ + s = strchr(passwd->bv_val, DELIM); + if (s) { + len = s - passwd->bv_val; + } else { + return rc; + } + if (!ber_str2bv(passwd->bv_val, len, 0, &otp)) + return rc; + + /* The static password part */ + s++; + ber_str2bv(s, 0, 0, &pass); + + /* Hash the OTP seed */ + rc = passwd_string32(scheme, &otp, &hash_otp); + + /* If successful, hash the static password, else cleanup and return */ + if (rc == LUTIL_PASSWD_OK) { + rc = lutil_passwd_hash(&pass, TOTP_AND_PW_HASH_SCHEME, + &hash_pass, text); + } else { + return LUTIL_PASSWD_ERR; + } + + /* If successful, allocate memory to combine them, else cleanup + * and return */ + if (rc == LUTIL_PASSWD_OK) { + /* Add 1 character to bv_len to hold DELIM */ + hash->bv_len = hash_pass.bv_len + hash_otp.bv_len + 1; + hash->bv_val = ber_memalloc(hash->bv_len + 1); + if (!hash->bv_val) + rc = LUTIL_PASSWD_ERR; + } else { + memset(hash_otp.bv_val, 0, hash_otp.bv_len); + ber_memfree(hash_otp.bv_val); + return LUTIL_PASSWD_ERR; + } + + /* If successful, combine the two hashes with the delimiter */ + if (rc == LUTIL_PASSWD_OK) { + AC_MEMCPY(hash->bv_val, hash_otp.bv_val, hash_otp.bv_len); + hash->bv_val[hash_otp.bv_len] = DELIM; + AC_MEMCPY(hash->bv_val + hash_otp.bv_len + 1, + hash_pass.bv_val, hash_pass.bv_len); + hash->bv_val[hash->bv_len] = '\0'; + } + + /* Cleanup and return */ + memset(hash_otp.bv_val, 0, hash_otp.bv_len); + memset(hash_pass.bv_val, 0, hash_pass.bv_len); + ber_memfree(hash_otp.bv_val); + ber_memfree(hash_pass.bv_val); + + return rc; +} + static int hash_totp1( const struct berval *scheme, const struct berval *passwd, @@ -586,6 +742,51 @@ return passwd_string32(scheme, passwd, hash); } +static int hash_totp1andpw( + 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 hash_totp_and_pw(scheme, passwd, hash, text); +} + +static int hash_totp256andpw( + 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 hash_totp_and_pw(scheme, passwd, hash, text); +} + +static int hash_totp512andpw( + 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 hash_totp_and_pw(scheme, passwd, hash, text); +} + static int totp_op_cleanup( Operation *op, SlapReply *rs ) @@ -769,6 +970,12 @@ 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) + rc = lutil_passwd_add((struct berval *) &scheme_totp1andpw, chk_totp1andpw, hash_totp1andpw); + if (!rc) + rc = lutil_passwd_add((struct berval *) &scheme_totp256andpw, chk_totp256andpw, hash_totp256andpw); + if (!rc) + rc = lutil_passwd_add((struct berval *) &scheme_totp512andpw, chk_totp512andpw, hash_totp512andpw); if (rc) return rc; diff -Naur totp.orig/slapo-totp.5 totp/slapo-totp.5 --- totp.orig/slapo-totp.5 2019-09-06 13:46:16.356077826 -0400 +++ totp/slapo-totp.5 2019-09-13 11:48:33.242313524 -0400 @@ -27,6 +27,12 @@ When prompted to authenticate, the user merely enters the six-digit code provided by the prover. +Additionally, the overlay can also authenticate TOTP passwords +combined with a static password. To do this, utilize one of the +{TOTP1ANDPW}, {TOTP256ANDPW}, or {TOTP512ANDPW} password schemes +and append the static password scheme value to the end of the +userPassword attribute, separated by a pipe (|) character. + This implementation complies with .B RFC 6238 TOTP Time-based One Time Passwords and includes support for the SHA-1, SHA-256, and SHA-512 HMAC @@ -39,7 +45,8 @@ .SH CONFIGURATION Once the module is loaded with the moduleload command from the synopsis, -the {TOTP1}, {TOTP256}, and {TOTP512} +the {TOTP1}, {TOTP256}, {TOTP512} +{TOTP1ANDPW}, {TOTP256ANDPW}, and {TOTP512ANDPW} password schemes will be recognized. On the databases where your users reside you must configure the @@ -68,6 +75,11 @@ the authTimestamp attribute to other servers that are providing authentication services. +The hash functions for the {TOTP1ANDPW}, {TOTP256ANDPW}, and {TOTP512ANDPW} +schemes expect the secret to be entered in the form: +, where DELIM is currently defined +as the pipe character (|). + .SH BUGS The time step is hard-coded to thirty seconds. This should be OK for many use cases, but it would be nice if the value @@ -78,9 +90,6 @@ this is probably better than the alternative length of four digits, there may be cases where a four-digit value is preferred. -There is currently no way to require a separate PIN code with the authenticator -code. - In cases where password-hash lists multiple mechanisms, the TOTP key will also be changed at the same time. This is likely to be undesirable behavior. @@ -89,3 +98,5 @@ .SH ACKNOWLEDGEMENT This work was developed by Howard Chu of Symas Corporation for inclusion in OpenLDAP Software. + +Password + TOTP support added by Greg Veldman on behalf of SCinet.