diff options
| -rw-r--r-- | ipaclient/install/client.py | 2 | ||||
| -rw-r--r-- | ipapython/ipautil.py | 116 | ||||
| -rw-r--r-- | ipaserver/install/certs.py | 4 | ||||
| -rw-r--r-- | ipaserver/install/dnskeysyncinstance.py | 7 | ||||
| -rw-r--r-- | ipaserver/install/dogtaginstance.py | 2 | ||||
| -rw-r--r-- | ipaserver/install/httpinstance.py | 2 | ||||
| -rw-r--r-- | ipaserver/plugins/baseuser.py | 8 | ||||
| -rw-r--r-- | ipaserver/plugins/host.py | 12 | ||||
| -rw-r--r-- | ipaserver/plugins/stageuser.py | 5 | ||||
| -rw-r--r-- | ipaserver/plugins/user.py | 5 | ||||
| -rw-r--r-- | ipaserver/secrets/store.py | 2 |
11 files changed, 106 insertions, 59 deletions
diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py index 60a5c180a..2ff612280 100644 --- a/ipaclient/install/client.py +++ b/ipaclient/install/client.py @@ -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) diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index f061e7961..408ca3fb0 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -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() - - 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 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 + def user_input(prompt, default = None, allow_empty = True): if default == None: diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index 414a71664..85c2d06c0 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -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) diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py index 76a14f9d9..861a1702e 100644 --- a/ipaserver/install/dnskeysyncinstance.py +++ b/ipaserver/install/dnskeysyncinstance.py @@ -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') diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py index dc4b5b071..c3c470d62 100644 --- a/ipaserver/install/dogtaginstance.py +++ b/ipaserver/install/dogtaginstance.py @@ -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')) diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index e8c706e7a..bacd5fc4f 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -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) diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py index 4c7e9f083..85ad41768 100644 --- a/ipaserver/plugins/baseuser.py +++ b/ipaserver/plugins/baseuser.py @@ -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']) diff --git a/ipaserver/plugins/host.py b/ipaserver/plugins/host.py index 957a1edcf..58e711f34 100644 --- a/ipaserver/plugins/host.py +++ b/ipaserver/plugins/host.py @@ -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: diff --git a/ipaserver/plugins/stageuser.py b/ipaserver/plugins/stageuser.py index 1da43ecb6..afd402ea2 100644 --- a/ipaserver/plugins/stageuser.py +++ b/ipaserver/plugins/stageuser.py @@ -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']) diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py index 529609314..64405483a 100644 --- a/ipaserver/plugins/user.py +++ b/ipaserver/plugins/user.py @@ -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']) diff --git a/ipaserver/secrets/store.py b/ipaserver/secrets/store.py index 1c369d8cd..2c58eeedb 100644 --- a/ipaserver/secrets/store.py +++ b/ipaserver/secrets/store.py @@ -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') |
