Roundup Tracker - Issues

Issue 2551171

classification
Add support for have I been powned password checking/email address
Type: security Severity: normal
Components: Web interface Versions:
process
Status: closed fixed
:
: rouilj : rouilj
Priority: normal : Effort-Low, StarterTicket

Created on 2021-11-24 22:36 by rouilj, last changed 2022-05-12 02:51 by rouilj.

Messages
msg7371 Author: [hidden] (rouilj) Date: 2021-11-24 22:36
Somebody asked if Roundup supported Have I been Powned (https://haveibeenpwned.com/)
using the password API:

  https://haveibeenpwned.com/API/v3

It doesn't but along the lines of https://wiki.roundup-tracker.org/TestPasswordComplexity
a similar mechanism can use:

 https://pypi.org/project/pyhibp/

to get a password check.

Also adding support for specifying an API key (https://haveibeenpwned.com/API/Key) in:

  detectors/config.ini

for the tracker allows use of checks for the email address(es) of the user that can be
done in a detector.
msg7425 Author: [hidden] (rouilj) Date: 2021-12-21 01:06
Adding this function allows checking the entered password for
exposure on have I been powned.

import hashlib
import requests
from roundup.exceptions import Reject

def check_pw_hibp(password, mode="change"):
   # encode to unicode string and return a hex encoded sha1 hash
   pwhash = hashlib.sha1(password.encode()).hexdigest()
   # search hibp using the first 5 hash characters
   try:
       rqst = requests.get('https://api.pwnedpasswords.com/range/' + pwhash[:5],
                        timeout=2.5) # timeout 2.5 seconds
   except requests.exceptions.Timeout:
       return False # don't let API failure stop login or change
   # turn string like FA31ED547EE63D61B812DABE2F37D2AAFE2:2\r\n<another hash>
   # into list element of FA31ED547EE63D61B812DABE2F37D2AAFE2
   res_hash_list = [ x.split(':')[0] for x in rqst.text.split('\r\n') ]
   # hibp returns hash residual after removing first 5 requested chars
   # so search for it.
   if pwhash.upper()[5:] in res_hash_list:
      if mode == 'change':
         # reject change of password to the password value
         raise Reject("Password has been exposed on have i been powned. ")
      elif mode == "login":
         # Evaluates to true - have been found on hibp. Allow
         # the user to login (and change password) but provide an error message.
         return "Warning password has been exposed on have i been powned. Change password"
   return False # false == not found in hibp

Using the technique in:

  https://wiki.roundup-tracker.org/TestPasswordComplexity

and calling this with mode="change" allows you to check it when it gets changed.

You can put this in the login flow using the technique in:

  https://wiki.roundup-tracker.org/LoginWithEmail

so the password is checked for exposure on every login. Use code like:

  r = check_pw_hibp(self.form['__login_password'].value)
  if r:
     self.client.add_error_message(r)
     
in the login action wrapper.

Probably checking in both places is a good idea. One makes sure the user
isn't starting off with an exposed password and the other verifies that it
is still unexposed.

I put this on the wiki as well.
https://wiki.roundup-tracker.org/CheckForExposedPasswordHIBP
msg7515 Author: [hidden] (rouilj) Date: 2022-05-12 02:51
Since this is documented on the wiki, I consider it done.
History
Date User Action Args
2022-05-12 02:51:25rouiljsetpriority: normal
assignee: rouilj
messages: + msg7515
status: new -> closed
resolution: fixed
2021-12-21 01:06:58rouiljsetmessages: + msg7425
2021-11-24 22:36:58rouiljcreate