mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
ipa_generate_password algorithm change
A change to the algorithm that generates random passwords for multiple purposes throught IPA. This spells out the need to assess password strength by the entropy it contains rather than its length. This new password generation should also be compatible with the NSS implementation of password requirements in FIPS environment so that newly created databases won't fail with wrong authentication. https://fedorahosted.org/freeipa/ticket/5695 Reviewed-By: Martin Basti <mbasti@redhat.com> Reviewed-By: Petr Spacek <pspacek@redhat.com>
This commit is contained in:
parent
8db5b277a0
commit
fb7c111ac1
@ -2296,7 +2296,7 @@ def create_ipa_nssdb():
|
||||
ipautil.backup_file(os.path.join(db.secdir, 'secmod.db'))
|
||||
|
||||
with open(pwdfile, 'w') as f:
|
||||
f.write(ipautil.ipa_generate_password(pwd_len=40))
|
||||
f.write(ipautil.ipa_generate_password())
|
||||
os.chmod(pwdfile, 0o600)
|
||||
|
||||
db.create_db(pwdfile)
|
||||
|
@ -23,6 +23,7 @@ import string
|
||||
import tempfile
|
||||
import subprocess
|
||||
import random
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import copy
|
||||
@ -51,8 +52,8 @@ from six.moves import urllib
|
||||
from ipapython.ipa_log_manager import root_logger
|
||||
from ipapython.dn import DN
|
||||
|
||||
GEN_PWD_LEN = 22
|
||||
GEN_TMP_PWD_LEN = 12 # only for OTP password that is manually retyped by user
|
||||
# only for OTP password that is manually retyped by user
|
||||
TMP_PWD_ENTROPY_BITS = 128
|
||||
|
||||
|
||||
PROTOCOL_NAMES = {
|
||||
@ -789,34 +790,89 @@ def parse_generalized_time(timestr):
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def ipa_generate_password(characters=None,pwd_len=None):
|
||||
''' Generates password. Password cannot start or end with a whitespace
|
||||
character. It also cannot be formed by whitespace characters only.
|
||||
Length of password as well as string of characters to be used by
|
||||
generator could be optionaly specified by characters and pwd_len
|
||||
parameters, otherwise default values will be used: characters string
|
||||
will be formed by all printable non-whitespace characters and space,
|
||||
pwd_len will be equal to value of GEN_PWD_LEN.
|
||||
'''
|
||||
if not characters:
|
||||
characters=string.digits + string.ascii_letters + string.punctuation + ' '
|
||||
else:
|
||||
if characters.isspace():
|
||||
raise ValueError("password cannot be formed by whitespaces only")
|
||||
if not pwd_len:
|
||||
pwd_len = GEN_PWD_LEN
|
||||
|
||||
upper_bound = len(characters) - 1
|
||||
rndpwd = ''
|
||||
r = random.SystemRandom()
|
||||
def ipa_generate_password(entropy_bits=256, uppercase=1, lowercase=1, digits=1,
|
||||
special=1, min_len=0):
|
||||
"""
|
||||
Generate token containing at least `entropy_bits` bits and with the given
|
||||
character restraints.
|
||||
|
||||
:param entropy_bits:
|
||||
The minimal number of entropy bits attacker has to guess:
|
||||
128 bits entropy: secure
|
||||
256 bits of entropy: secure enough if you care about quantum
|
||||
computers
|
||||
|
||||
Integer values specify minimal number of characters from given
|
||||
character class and length.
|
||||
Value None prevents given character from appearing in the token.
|
||||
|
||||
Example:
|
||||
TokenGenerator(uppercase=3, lowercase=3, digits=0, special=None)
|
||||
|
||||
At least 3 upper and 3 lower case ASCII chars, may contain digits,
|
||||
no special chars.
|
||||
"""
|
||||
special_chars = '!$%&()*+,-./:;<>?@[]^_{|}~'
|
||||
pwd_charsets = {
|
||||
'uppercase': {
|
||||
'chars': string.ascii_uppercase,
|
||||
'entropy': math.log(len(string.ascii_uppercase), 2)
|
||||
},
|
||||
'lowercase': {
|
||||
'chars': string.ascii_lowercase,
|
||||
'entropy': math.log(len(string.ascii_lowercase), 2)
|
||||
},
|
||||
'digits': {
|
||||
'chars': string.digits,
|
||||
'entropy': math.log(len(string.digits), 2)
|
||||
},
|
||||
'special': {
|
||||
'chars': special_chars,
|
||||
'entropy': math.log(len(special_chars), 2)
|
||||
},
|
||||
}
|
||||
req_classes = dict(
|
||||
uppercase=uppercase,
|
||||
lowercase=lowercase,
|
||||
digits=digits,
|
||||
special=special
|
||||
)
|
||||
# 'all' class is used when adding entropy to too-short tokens
|
||||
# it contains characters from all allowed classes
|
||||
pwd_charsets['all'] = {
|
||||
'chars': ''.join([
|
||||
charclass['chars'] for charclass_name, charclass
|
||||
in pwd_charsets.items()
|
||||
if req_classes[charclass_name] is not None
|
||||
])
|
||||
}
|
||||
pwd_charsets['all']['entropy'] = math.log(
|
||||
len(pwd_charsets['all']['chars']), 2)
|
||||
rnd = random.SystemRandom()
|
||||
|
||||
todo_entropy = entropy_bits
|
||||
password = ''
|
||||
# Generate required character classes:
|
||||
# The order of generated characters is fixed to comply with check in
|
||||
# NSS function sftk_newPinCheck() in nss/lib/softoken/fipstokn.c.
|
||||
for charclass_name in ['digits', 'uppercase', 'lowercase', 'special']:
|
||||
charclass = pwd_charsets[charclass_name]
|
||||
todo_characters = req_classes[charclass_name]
|
||||
while todo_characters > 0:
|
||||
password += rnd.choice(charclass['chars'])
|
||||
todo_entropy -= charclass['entropy']
|
||||
todo_characters -= 1
|
||||
|
||||
# required character classes do not provide sufficient entropy
|
||||
# or does not fulfill minimal length constraint
|
||||
allchars = pwd_charsets['all']
|
||||
while todo_entropy > 0 or len(password) < min_len:
|
||||
password += rnd.choice(allchars['chars'])
|
||||
todo_entropy -= allchars['entropy']
|
||||
|
||||
return password
|
||||
|
||||
for x in range(pwd_len):
|
||||
rndchar = characters[r.randint(0,upper_bound)]
|
||||
if (x == 0) or (x == pwd_len-1):
|
||||
while rndchar.isspace():
|
||||
rndchar = characters[r.randint(0,upper_bound)]
|
||||
rndpwd += rndchar
|
||||
return rndpwd
|
||||
|
||||
def user_input(prompt, default = None, allow_empty = True):
|
||||
if default == None:
|
||||
|
@ -173,7 +173,7 @@ class CertDB(object):
|
||||
if ipautil.file_exists(self.noise_fname):
|
||||
os.remove(self.noise_fname)
|
||||
f = open(self.noise_fname, "w")
|
||||
f.write(ipautil.ipa_generate_password(pwd_len=25))
|
||||
f.write(ipautil.ipa_generate_password())
|
||||
self.set_perms(self.noise_fname)
|
||||
|
||||
def create_passwd_file(self, passwd=None):
|
||||
@ -182,7 +182,7 @@ class CertDB(object):
|
||||
if passwd is not None:
|
||||
f.write("%s\n" % passwd)
|
||||
else:
|
||||
f.write(ipautil.ipa_generate_password(pwd_len=25))
|
||||
f.write(ipautil.ipa_generate_password())
|
||||
f.close()
|
||||
self.set_perms(self.passwd_fname)
|
||||
|
||||
|
@ -224,10 +224,11 @@ class DNSKeySyncInstance(service.Service):
|
||||
os.chown(paths.DNSSEC_TOKENS_DIR, self.ods_uid, self.named_gid)
|
||||
|
||||
# generate PINs for softhsm
|
||||
allowed_chars = u'123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
pin_length = 30 # Bind allows max 32 bytes including ending '\0'
|
||||
pin = ipautil.ipa_generate_password(allowed_chars, pin_length)
|
||||
pin_so = ipautil.ipa_generate_password(allowed_chars, pin_length)
|
||||
pin = ipautil.ipa_generate_password(
|
||||
entropy_bits=0, special=None, min_len=pin_length)
|
||||
pin_so = ipautil.ipa_generate_password(
|
||||
entropy_bits=0, special=None, min_len=pin_length)
|
||||
|
||||
self.logger.debug("Saving user PIN to %s", paths.DNSSEC_SOFTHSM_PIN)
|
||||
named_fd = open(paths.DNSSEC_SOFTHSM_PIN, 'w')
|
||||
|
@ -427,7 +427,7 @@ class DogtagInstance(service.Service):
|
||||
|
||||
def setup_admin(self):
|
||||
self.admin_user = "admin-%s" % self.fqdn
|
||||
self.admin_password = ipautil.ipa_generate_password(pwd_len=20)
|
||||
self.admin_password = ipautil.ipa_generate_password()
|
||||
self.admin_dn = DN(('uid', self.admin_user),
|
||||
('ou', 'people'), ('o', 'ipaca'))
|
||||
|
||||
|
@ -313,7 +313,7 @@ class HTTPInstance(service.Service):
|
||||
ipautil.backup_file(nss_path)
|
||||
|
||||
# Create the password file for this db
|
||||
password = ipautil.ipa_generate_password(pwd_len=15)
|
||||
password = ipautil.ipa_generate_password()
|
||||
f = os.open(pwd_file, os.O_CREAT | os.O_RDWR)
|
||||
os.write(f, password)
|
||||
os.close(f)
|
||||
|
@ -17,8 +17,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import string
|
||||
|
||||
import six
|
||||
|
||||
from ipalib import api, errors
|
||||
@ -35,7 +33,7 @@ from ipalib.request import context
|
||||
from ipalib import _
|
||||
from ipalib.constants import PATTERN_GROUPUSER_NAME
|
||||
from ipapython import kerberos
|
||||
from ipapython.ipautil import ipa_generate_password, GEN_TMP_PWD_LEN
|
||||
from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
|
||||
from ipapython.ipavalidate import Email
|
||||
from ipalib.util import (
|
||||
normalize_sshpubkey,
|
||||
@ -75,8 +73,6 @@ UPG_DEFINITION_DN = DN(('cn', 'UPG Definition'),
|
||||
('cn', 'etc'),
|
||||
api.env.basedn)
|
||||
|
||||
# characters to be used for generating random user passwords
|
||||
baseuser_pwdchars = string.digits + string.ascii_letters + '_,.@+-='
|
||||
|
||||
def validate_nsaccountlock(entry_attrs):
|
||||
if 'nsaccountlock' in entry_attrs:
|
||||
@ -554,7 +550,7 @@ class baseuser_mod(LDAPUpdate):
|
||||
def check_userpassword(self, entry_attrs, **options):
|
||||
if 'userpassword' not in entry_attrs and options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(
|
||||
baseuser_pwdchars, pwd_len=GEN_TMP_PWD_LEN)
|
||||
entropy_bits=TMP_PWD_ENTROPY_BITS)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
|
||||
|
@ -21,7 +21,6 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import dns.resolver
|
||||
import string
|
||||
|
||||
import six
|
||||
|
||||
@ -62,7 +61,7 @@ from ipalib.util import (normalize_sshpubkey, validate_sshpubkey_no_options,
|
||||
from ipapython.ipautil import (
|
||||
ipa_generate_password,
|
||||
CheckedIPAddress,
|
||||
GEN_TMP_PWD_LEN
|
||||
TMP_PWD_ENTROPY_BITS
|
||||
)
|
||||
from ipapython.dnsutil import DNSName
|
||||
from ipapython.ssh import SSHPublicKey
|
||||
@ -136,10 +135,6 @@ EXAMPLES:
|
||||
|
||||
register = Registry()
|
||||
|
||||
# Characters to be used by random password generator
|
||||
# The set was chosen to avoid the need for escaping the characters by user
|
||||
host_pwd_chars = string.digits + string.ascii_letters + '_,.@+-='
|
||||
|
||||
|
||||
def remove_ptr_rec(ipaddr, fqdn):
|
||||
"""
|
||||
@ -688,7 +683,7 @@ class host_add(LDAPCreate):
|
||||
entry_attrs['objectclass'].remove('krbprincipal')
|
||||
if options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(
|
||||
characters=host_pwd_chars, pwd_len=GEN_TMP_PWD_LEN)
|
||||
entropy_bits=TMP_PWD_ENTROPY_BITS)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
certs = options.get('usercertificate', [])
|
||||
@ -915,7 +910,8 @@ class host_mod(LDAPUpdate):
|
||||
entry_attrs['usercertificate'] = certs_der
|
||||
|
||||
if options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(characters=host_pwd_chars)
|
||||
entry_attrs['userpassword'] = ipa_generate_password(
|
||||
entropy_bits=TMP_PWD_ENTROPY_BITS)
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
|
||||
if 'macaddress' in entry_attrs:
|
||||
|
@ -38,7 +38,6 @@ from .baseuser import (
|
||||
baseuser_find,
|
||||
baseuser_show,
|
||||
NO_UPG_MAGIC,
|
||||
baseuser_pwdchars,
|
||||
baseuser_output_params,
|
||||
baseuser_add_manager,
|
||||
baseuser_remove_manager)
|
||||
@ -47,7 +46,7 @@ from ipalib.util import set_krbcanonicalname
|
||||
from ipalib import _, ngettext
|
||||
from ipalib import output
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.ipautil import ipa_generate_password, GEN_TMP_PWD_LEN
|
||||
from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
|
||||
from ipalib.capabilities import client_has_capability
|
||||
|
||||
if six.PY3:
|
||||
@ -340,7 +339,7 @@ class stageuser_add(baseuser_add):
|
||||
# If requested, generate a userpassword
|
||||
if 'userpassword' not in entry_attrs and options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(
|
||||
baseuser_pwdchars, pwd_len=GEN_TMP_PWD_LEN)
|
||||
entropy_bits=TMP_PWD_ENTROPY_BITS)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
|
||||
|
@ -38,7 +38,6 @@ from .baseuser import (
|
||||
NO_UPG_MAGIC,
|
||||
UPG_DEFINITION_DN,
|
||||
baseuser_output_params,
|
||||
baseuser_pwdchars,
|
||||
validate_nsaccountlock,
|
||||
convert_nsaccountlock,
|
||||
fix_addressbook_permission_bindrule,
|
||||
@ -63,7 +62,7 @@ from ipalib import _, ngettext
|
||||
from ipalib import output
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.dn import DN
|
||||
from ipapython.ipautil import ipa_generate_password, GEN_TMP_PWD_LEN
|
||||
from ipapython.ipautil import ipa_generate_password, TMP_PWD_ENTROPY_BITS
|
||||
from ipalib.capabilities import client_has_capability
|
||||
|
||||
if api.env.in_server:
|
||||
@ -529,7 +528,7 @@ class user_add(baseuser_add):
|
||||
|
||||
if 'userpassword' not in entry_attrs and options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(
|
||||
baseuser_pwdchars, pwd_len=GEN_TMP_PWD_LEN)
|
||||
entropy_bits=TMP_PWD_ENTROPY_BITS)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
|
||||
|
@ -122,7 +122,7 @@ class NSSCertDB(DBMAPHandler):
|
||||
with open(nsspwfile, 'w+') as f:
|
||||
f.write(self.nssdb_password)
|
||||
pk12pwfile = os.path.join(tdir, 'pk12pwfile')
|
||||
password = ipautil.ipa_generate_password(pwd_len=20)
|
||||
password = ipautil.ipa_generate_password()
|
||||
with open(pk12pwfile, 'w+') as f:
|
||||
f.write(password)
|
||||
pk12file = os.path.join(tdir, 'pk12file')
|
||||
|
Loading…
Reference in New Issue
Block a user