summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipaclient/install/client.py2
-rw-r--r--ipapython/ipautil.py116
-rw-r--r--ipaserver/install/certs.py4
-rw-r--r--ipaserver/install/dnskeysyncinstance.py7
-rw-r--r--ipaserver/install/dogtaginstance.py2
-rw-r--r--ipaserver/install/httpinstance.py2
-rw-r--r--ipaserver/plugins/baseuser.py8
-rw-r--r--ipaserver/plugins/host.py12
-rw-r--r--ipaserver/plugins/stageuser.py5
-rw-r--r--ipaserver/plugins/user.py5
-rw-r--r--ipaserver/secrets/store.py2
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')