ltb-project / self-service-password

Web interface to change and reset password in an LDAP directory
https://self-service-password.readthedocs.io/en/latest/
GNU General Public License v3.0
1.16k stars 325 forks source link

request: TOTP (2FA) #27

Open r2evans opened 8 years ago

r2evans commented 8 years ago

I had initially suggested this in http://tools.ltb-project.org/issues/818. For reference:

Provide 2FA (TFA) as an option for verification of the user. There are some 2FA PHP modules (https://github.com/RobThree/TwoFactorAuth, https://github.com/PHPGangsta/GoogleAuthenticator) that provide sufficient access to implement it.

(It appears that the second link hasn't been touched in a couple of years, perhaps RobThree's would be preferred?)

I recognize it is not necessarily a top priority for all (or perhaps many, even), so I was considering how hard it would be to implement. Some questions I had:

The current method of using SMS as a reset mechanism is useful but this sort of 2FA is no longer recommended. I'm inferring that this should also apply to email-based OOB reset links, for similar reasons.

Thoughts?

coudot commented 8 years ago

Yes this is interesting but clearly not a priority for the moment.

aurbjo commented 8 years ago

Just to let you know, SecurEnvoy 2FA uses the telexNumber attribute i AD to store the shared secret, this is an attribute that is most likely not used by anyone and most people don't even know it exists. Makes it very easy to check for users with 2FA enabled as all users that have not null in the telexNumber field are enabled.

r2evans commented 8 years ago

@aurbjo thanks for the feedback (https://www.securenvoy.com/ for reference). @coudot, it's a subscription service, though I don't think aurbjo was suggesting it as anything other than a place to stash the token.

I don't find that field in my AD schema, so I don't know if it's easy enough to add it ad hoc or if the schema needs to be formally updated (which was more the point of my first post here). Coudot, since you're the expert here on LDAP, can you say if it's easy, it's hard, or "it depends"?

I'm sure you haven't been able to put a lot of thought to this yet. Would you envision 2FA being a component added on to others? For example, after sendsms or sendtoken (and pre-registered 2FA), the user would be asked for "new password", "confirm new password", and "2FA code".

Rationale: SMS-based 2FA has been deprecated by itself and email-based links are just as susceptible. However, compounding with TOTP would make it a little harder to break.

Thanks again, v1.0 is working like a champ!

coudot commented 8 years ago

I would indeed prefer to implement TOTP or HOTP because these are standards and you could use a free client like FreeOTP to use it.

OTP is quite like a captcha: an additional check. So we could add an option to add OTP in forms like we already do for captcha.

Links to check for the server-side implementation:

r2evans commented 8 years ago

The latter is unmaintained, recommending a still-maintained fork: https://github.com/Spomky-Labs/otphp. Both seem fine, I haven't read enough to have an idea of which would be better.

For storing the token, does LDAP support adding arbitrary fields to a user's record? If not, is it necessary to update the schema or is it acceptable to hijack one of the existing fields?

flhoest commented 7 years ago

Very interesting post, I was actually here to search for such feature. Our company is requesting OTP when reseting password :-/ Any idea when this could be made available ?

coudot commented 7 years ago

OTP can be used either as a second authentication factor (kind like a captcha) or as a replacement of mail or SMS. In both cases a secret should be kept in LDAP user's entry.

I don't know how long it could take to implement the solution, but I'm not sure to be able to do it on my spare time.

flhoest commented 7 years ago

This is why they invented GitHub ;)

r2evans commented 7 years ago

@flhoest, does this mean you're planning to work on it? I have it on my todo list but haven't been able to commit time to it yet.

@coudot, this requires adding to the LDAP/AD schema. I suspect that since we need to create a new object class that inherits from an existing one, is it safe enough to assume organizationalPerson, or is inetOrgPerson better to use? Since I see neither in the SSP code, but is there a third I'm not considering?

flhoest commented 7 years ago

@r2evans: I'm afraid I do not have the skills. I'm just an advanced user.

coudot commented 7 years ago

@r2evans extending the schema is not mandatory. See the reset by questions feature, I coded it so we can use any objectClass/attribute to store/read the answer. You should not force someone to extend the schema, as you don't know which kind of LDAP directory he is running (OpenLDAP, AD, etc.)

r2evans commented 7 years ago

Thanks for the pointer. I've been trying to learn the code but get an LDAP error.

Using command-line php (actually, psysh), I manually load all of index.php and pages/setquestions.php in order to connect successfully to the AD. I connect with my user, not the admin user (or manager), I think this is the correct method.

I set a question to be:

>>> $userdata[$answer_attribute] = '{favice}vanilla';
>>> $userdata
=> [
     "objectClass" => [
       "top",
       "person",
       "organizationalPerson",
       "user",
       "extensibleObject",
     ],
     "info" => "{favice}vanilla",
   ]

("favice" is defined as $messages["questions"]["favice"] = ... in conf/config.inc.php.)

However, when I get down to:

    # Commit modification on directory
    $replace = ldap_mod_replace($ldap, $userdn , $userdata);

I get:

PHP warning:  ldap_mod_replace(): Modify: No such attribute on line 1

(This happens with an array with either or both of objectClass and info.)

Should I be changing to the manager role at some point to be able to overwrite object attributes?

coudot commented 7 years ago

extensibleObject is not compatible with AD, you need to choose an objectClass and/or an attribute compatible with AD schema.

r2evans commented 7 years ago

Huh, didn't know that. Does this mean that "questions" won't work in an AD environment? I don't see any conditioning of storage location based on $ad_mode. I'm not able currently to test it, so I can't answer that myself ... this is what I see with an AD (attained using ldbedit and samba4):

dn: CN=Bill Evans,CN=Users,DC=example,DC=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user

and many other fields: accountExpires, badPasswordTime, badPwdCount, cn, codePage, countryCode, displayName, distinguishedName, givenName, instanceType, lastLogoff, lastLogon, lastLogonTimestamp, logonCount, mail, memberOf, memberOf, memberOf, name, objectCategory, objectGUID, objectSid, primaryGroupID, pwdLastSet, sAMAccountName, sAMAccountType, sn, userAccountControl, userPrincipalName, uSNChanged, uSNCreated, whenChanged, and whenCreated. Nothing that appears to be "available" for modifying in this vein.

But I'm guessing what you're saying is that I need to find something to put in objectClass, a multi-value "property that identifies the class of which the object is an instance, as well as all structural or abstract superclasses from which that class is derived".1 MSDN says that this includes (just) these four options for users. However, since I'm implementing with Samba4, I don't know if the restrictions are there as well. I tried adding contact (suggested on technet2) but got the same error. I also tried this with $who_change_password set to "user" and "manager" with no change.

Any thoughts on what might work? I don't know enough about LDAP (internals) to be able to make intelligent guesses.

  1. https://msdn.microsoft.com/en-us/library/ms677612(v=vs.85).aspx
  2. http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Filter_on_objectCategory_and_objectClass
coudot commented 7 years ago

Questions can work with AD, you just have to define in which attribute you will store answers. For test, you can use:

$answer_objectClass = "user";
$answer_attribute = "description";

But this might not be a good idea as description can be read by other user by default. You then need to work on access control in AD to protect the visibility on chosen attribute.

r2evans commented 7 years ago

Alright, I'm learning. I successfully added to organizationalPerson an attribute telexNumber (thanks again, @aurbjo). Unfortunately, it is visible to other users. I've been looking around (such as at zytrax) and cannot find a way to change it. I think this may have something to do with attribute syntaxes and attribute types, as discussed here (at ldap.com), but have not figured it out.

@coudot, do you know how to mark a new attribute as private?

coudot commented 7 years ago

@r2evans never done that, but you can look at https://support.microsoft.com/en-us/kb/218596

r2evans commented 7 years ago

I still like your previous comment, "You should not force someone to extend the schema", which I am further interpreting to mean "make changes to the schema or server configuration". I would much prefer this to be a plug-and-play solution.

@aurbjo, do you know if SecurEnvoy is making the "telexNumber" private somehow, or are they assuming its obscurity will prevent most people from sneaking a peek at somebody else's OTP key?

aurbjo commented 7 years ago

@r2evans SecurEnvoy does not make the field private, they just encrypt it.

I think it would be possible to make e private field by adding a custom deny ACL in AD, but i think it would be better to encrypt the value that's stored in the field because it would be a "plug and play" solution as you're already mentioning.

Every time a successful 2FA happens the field is updated with the new encrypted value. They also use the field to store other information, like the approved 2FA communication paths like mobile phone number or email address. Personally i think this is a neat way as it does not require any additional config in AD

r2evans commented 7 years ago

I think that's a great idea, thanks for the suggestion. I think this is perhaps the way I'll move forward with it.

  1. All storage will be in organizationPerson/telexNumber. We can make a note in the docs suggesting if/how the admin might make this private, but designing around the assumption that it will be public is a good thing, I think.
  2. Encrypt it using $keyphrase (used for other things within SSP). One side-effect of this is that if/when this key is lost/changed, all OTPs will become invalid.
  3. Something @aurbjo said is striking a thought: though I don't see the hard-need at the moment, is there any reason to not allow this storage to have multiple disparate items? I'm thinking something like a JSON dictionary, something like either:

    # forgive me momentarily the pseudo-code
    $telexenc = ldap_get_values($ldap, $entry, "objectClass")["organizationalPerson/telexNumber"];
    $telexjson = decrypt($telexenc, $keyphrase);
    $telex = json_decode($telexjson);
    # {
    #   "totp": {
    #     "cred": "otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30",
    #     "backup": ["53392924", "63086100", "44801392", "49539614"],
    #     "issued": "2016-11-28 09:03:27"
    #   }
    # } 
    

    with the entire storage encrypted (my preference), or perhaps:

    $telexjson = ldap_get_values($ldap, $entry, "objectClass")["organizationalPerson/telexNumber"];
    # {"totp":["158dbbaad8cf1c9faca5414f31fdc6d67a515fe3a377b348343fd1d6a52f790a6d5318a35bfa591cac02a5947a1903ff662fc94283d15242261b60e01a3acad4a55cafcb4c742585dce07257f89422690fcb32d67fbf3f82741c5c1682d87201066e6ef596d22efd87ea155c5eee6128ce4e22b36b86548d662f7059241af5441da2df026eaf3068d328a7244bf2b78d7c060a84d11a"]} 
    $telex = json_decode($telexjson);
    $otpcred = decrypt($telex["totp"], $keyphrase)

    encrypting each entry. Though I prefer the first (encrypting the whole store), if there are any components where privacy is not a concern, the second would work too.

It might be useful to have a mechanism for changing the $keyphrase, thereby de- and re-encrypting relevant telexNumber entries. I don't think having a web page for this function is required (or even advisable), so I suggest a command-line tool that, given both the old and the new key, iterates over all entries or a list of entries (e.g., search criteria under a DN) and re-encrypts it. This is definitely a not priority at the moment but may be nice to have in the future.

Though migration would be wise, I think this could also be used to store the questions as well. That is, perhaps telexNumber (decrypted) could look something like:

{
  "totp": {
    "cred": "otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30",
    "backup": ["53392924", "63086100", "44801392", "49539614"],
    "issued": "2016-11-28 09:03:27"
  },
  "questions": [
    {
      "q": "favice",
      "a": "chocolate",
      "issued": "2016-11-28 09:03:27"
    },
    {
      "q": "petname",
      "a": "rover",
      "issued": "2016-11-28 09:03:27"
    },
    {
      "q": "what is your favorite football team?",
      "a": "manchester united",
      "issued": "2016-11-28 09:03:27"
    }
  ]
} 

using preset questions (first two) and/or optionally custom questions (third). There's no reason other fields couldn't be added later (e.g., to add HOTP) without impacting current methods.

coudot commented 7 years ago

Ye, it's a good idea to encrypt data inside LDAP attribute, this could be an option for the next release. We should support to read old format data anc convert them to new format (encrypted JSON)

yuki commented 6 years ago

I have compiled the TOTP module for OpenLDAP (https://github.com/openldap/openldap/tree/master/contrib/slapd-modules/passwd/totp) , and I have it working (for testing right now) in a Debian 9. What I've done is download the OpenLdap source from Debian (apt-get source slapd), compile it, copy the totp module into the debian code, compile it, and copy the dompiled module into /usr/lib/slapd

How does it work?

This works as other passwords. The password is stored in LDAP, in the userPassword field like "{TOTP1}base32_hash" (like other passwords that are stored as "{CRYPT}asdasd" for example). What the user has to do is scan the "base32_hash" in a qrcode format with FreeOTP. Better explained in https://sys4.de/en/blog/2015/11/09/totp-time-based-one-time-password-authentication/

The user can have two passwords, one based on TOTP and other crypted (with crypt, ssha, whatever). What I'm not sure is how to reset one of the passowrds, but not the other :-/

What the application must do?

Because how the module works, it's like a normal password authentication. The user introduces the username and the TOTP, and the LDAP returns if OK or "credential invalid".

I have seen this project https://github.com/Arno0x/TwoFactorAuth . It creates a qrcode based on the hash and store it in a sqlite code. The idea of this project is to create a "captive portal" using nginx's auth_request module.

I need to create a web where the user gets a qrcode to get a new TOTP as the previous project. Not sure if my users will have two separated passwords, just a TOTP, or a secondary user which will have the TOTP only. I'm using your project for users can reset their password. So..

What's the idea?

I'm going to try to add an option to the project to reset the password as a TOTP, which creates a qrcode to be scanned. I haven't see your code, I have no idea how I'm going to do it yet, but the next week I'll try to have a functional approach.