2017-03-31 15:50:31 +13:00
# Tests for Tests for source4/dsdb/samdb/ldb_modules/password_hash.c
#
# Copyright (C) Catalyst IT Ltd. 2017
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Base class for tests for source4 / dsdb / samdb / ldb_modules / password_hash . c
"""
from samba . credentials import Credentials
from samba . samdb import SamDB
from samba . auth import system_session
from samba . tests import TestCase
from samba . ndr import ndr_unpack
from samba . dcerpc import drsblobs
from samba . dcerpc . samr import DOMAIN_PASSWORD_STORE_CLEARTEXT
from samba . dsdb import UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
from samba . tests import delete_force
2018-05-11 11:03:03 +12:00
from samba . tests . password_test import PasswordCommon
2017-03-31 15:50:31 +13:00
import ldb
import samba
import binascii
2018-02-01 12:01:10 +13:00
from hashlib import md5
2017-04-12 09:12:56 +12:00
import crypt
2017-03-31 15:50:31 +13:00
USER_NAME = " PasswordHashTestUser "
2017-04-12 09:09:27 +12:00
USER_PASS = samba . generate_random_password ( 32 , 32 )
2017-03-31 15:50:31 +13:00
UPN = " PWHash@User.Principle "
# Get named package from the passed supplemental credentials
#
# returns the package and it's position within the supplemental credentials
2018-07-30 18:20:39 +12:00
2017-03-31 15:50:31 +13:00
def get_package ( sc , name ) :
if sc is None :
return None
idx = 0
for p in sc . sub . packages :
idx + = 1
if name == p . name :
return ( idx , p )
return None
# Calculate the MD5 password digest from the supplied user, realm and password
#
2018-07-30 18:20:39 +12:00
2017-03-31 15:50:31 +13:00
def calc_digest ( user , realm , password ) :
data = " %s : %s : %s " % ( user , realm , password )
2020-07-04 14:27:06 +12:00
if isinstance ( data , str ) :
2018-05-04 12:05:27 +01:00
data = data . encode ( ' utf8 ' )
2018-02-01 12:01:10 +13:00
return md5 ( data ) . hexdigest ( )
2017-03-31 15:50:31 +13:00
class PassWordHashTests ( TestCase ) :
def setUp ( self ) :
2017-05-22 09:49:17 +12:00
self . lp = samba . tests . env_loadparm ( )
2017-03-31 15:50:31 +13:00
super ( PassWordHashTests , self ) . setUp ( )
2018-05-07 17:33:51 +12:00
def set_store_cleartext ( self , cleartext ) :
# get the current pwdProperties
pwdProperties = self . ldb . get_pwdProperties ( )
# update the clear-text properties flag
props = int ( pwdProperties )
if cleartext :
props | = DOMAIN_PASSWORD_STORE_CLEARTEXT
else :
props & = ~ DOMAIN_PASSWORD_STORE_CLEARTEXT
self . ldb . set_pwdProperties ( str ( props ) )
2017-03-31 15:50:31 +13:00
# Add a user to ldb, this will exercise the password_hash code
# and calculate the appropriate supplemental credentials
2017-05-22 09:49:17 +12:00
def add_user ( self , options = None , clear_text = False , ldb = None ) :
2017-03-31 15:50:31 +13:00
# set any needed options
if options is not None :
2017-04-12 09:09:27 +12:00
for ( option , value ) in options :
2017-03-31 15:50:31 +13:00
self . lp . set ( option , value )
2017-05-22 09:49:17 +12:00
if ldb is None :
self . creds = Credentials ( )
self . session = system_session ( )
self . creds . guess ( self . lp )
self . session = system_session ( )
self . ldb = SamDB ( session_info = self . session ,
credentials = self . creds ,
lp = self . lp )
else :
self . ldb = ldb
res = self . ldb . search ( base = self . ldb . get_config_basedn ( ) ,
expression = " ncName= %s " % self . ldb . get_default_basedn ( ) ,
attrs = [ " nETBIOSName " ] )
2018-05-09 11:24:48 +01:00
self . netbios_domain = str ( res [ 0 ] [ " nETBIOSName " ] [ 0 ] )
2017-05-22 09:49:17 +12:00
self . dns_domain = self . ldb . domain_dns_name ( )
2017-03-31 15:50:31 +13:00
# Gets back the basedn
base_dn = self . ldb . domain_dn ( )
# Gets back the configuration basedn
configuration_dn = self . ldb . get_config_basedn ( ) . get_linearized ( )
2018-05-11 11:03:03 +12:00
# permit password changes during this test
PasswordCommon . allow_password_changes ( self , self . ldb )
2017-03-31 15:50:31 +13:00
self . base_dn = self . ldb . domain_dn ( )
account_control = 0
if clear_text :
2018-05-07 17:33:51 +12:00
# Restore the current domain setting on exit.
2017-03-31 15:50:31 +13:00
pwdProperties = self . ldb . get_pwdProperties ( )
self . addCleanup ( self . ldb . set_pwdProperties , pwdProperties )
2018-05-07 17:33:51 +12:00
# Update the domain setting
self . set_store_cleartext ( clear_text )
2017-03-31 15:50:31 +13:00
account_control | = UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
# (Re)adds the test user USER_NAME with password USER_PASS
# and userPrincipalName UPN
delete_force ( self . ldb , " cn= " + USER_NAME + " ,cn=users, " + self . base_dn )
self . ldb . add ( {
" dn " : " cn= " + USER_NAME + " ,cn=users, " + self . base_dn ,
" objectclass " : " user " ,
" sAMAccountName " : USER_NAME ,
" userPassword " : USER_PASS ,
" userPrincipalName " : UPN ,
" userAccountControl " : str ( account_control )
} )
# Get the supplemental credentials for the user under test
def get_supplemental_creds ( self ) :
base = " cn= " + USER_NAME + " ,cn=users, " + self . base_dn
res = self . ldb . search ( scope = ldb . SCOPE_BASE ,
base = base ,
attrs = [ " supplementalCredentials " ] )
2017-04-12 09:09:27 +12:00
self . assertIs ( True , len ( res ) > 0 )
2017-03-31 15:50:31 +13:00
obj = res [ 0 ]
sc_blob = obj [ " supplementalCredentials " ] [ 0 ]
sc = ndr_unpack ( drsblobs . supplementalCredentialsBlob , sc_blob )
return sc
# Calculate and validate a Wdigest value
2018-07-30 18:19:11 +12:00
def check_digest ( self , user , realm , password , digest ) :
2017-04-12 09:09:27 +12:00
expected = calc_digest ( user , realm , password )
2018-05-09 11:24:48 +01:00
actual = binascii . hexlify ( bytearray ( digest ) ) . decode ( ' utf8 ' )
2017-03-31 15:50:31 +13:00
error = " Digest expected[ %s ], actual[ %s ], " \
" user[ %s ], realm[ %s ], pass[ %s ] " % \
( expected , actual , user , realm , password )
2020-02-07 11:02:38 +13:00
self . assertEqual ( expected , actual , error )
2017-03-31 15:50:31 +13:00
# Check all of the 29 expected WDigest values
#
def check_wdigests ( self , digests ) :
2020-02-07 11:02:38 +13:00
self . assertEqual ( 29 , digests . num_hashes )
2017-03-31 15:50:31 +13:00
2017-05-18 14:38:37 +12:00
# Using the n-1 pattern in the array indexes to make it easier
# to check the tests against the spec and the samba-tool user tests.
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
2017-05-22 09:49:17 +12:00
self . netbios_domain ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 1 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . lower ( ) ,
2017-05-22 09:49:17 +12:00
self . netbios_domain . lower ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 2 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . upper ( ) ,
2017-05-22 09:49:17 +12:00
self . netbios_domain . upper ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 3 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
2017-05-22 09:49:17 +12:00
self . netbios_domain . upper ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 4 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
2017-05-22 09:49:17 +12:00
self . netbios_domain . lower ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 5 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . upper ( ) ,
2017-05-22 09:49:17 +12:00
self . netbios_domain . lower ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 6 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . lower ( ) ,
2017-05-22 09:49:17 +12:00
self . netbios_domain . upper ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 7 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
2017-05-22 09:49:17 +12:00
self . dns_domain ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 8 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . lower ( ) ,
2017-05-22 09:49:17 +12:00
self . dns_domain . lower ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 9 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . upper ( ) ,
2017-05-22 09:49:17 +12:00
self . dns_domain . upper ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 10 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
2017-05-22 09:49:17 +12:00
self . dns_domain . upper ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 11 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
2017-05-22 09:49:17 +12:00
self . dns_domain . lower ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 12 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . upper ( ) ,
2017-05-22 09:49:17 +12:00
self . dns_domain . lower ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 13 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . lower ( ) ,
2017-05-22 09:49:17 +12:00
self . dns_domain . upper ( ) ,
2017-03-31 15:50:31 +13:00
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 14 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( UPN ,
" " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 15 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( UPN . lower ( ) ,
" " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 16 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( UPN . upper ( ) ,
" " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 17 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
2017-05-22 09:49:17 +12:00
name = " %s \\ %s " % ( self . netbios_domain , USER_NAME )
2017-03-31 15:50:31 +13:00
self . check_digest ( name ,
" " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 18 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
2017-05-22 09:49:17 +12:00
name = " %s \\ %s " % ( self . netbios_domain . lower ( ) , USER_NAME . lower ( ) )
2017-03-31 15:50:31 +13:00
self . check_digest ( name ,
" " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 19 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
2017-05-22 09:49:17 +12:00
name = " %s \\ %s " % ( self . netbios_domain . upper ( ) , USER_NAME . upper ( ) )
2017-03-31 15:50:31 +13:00
self . check_digest ( name ,
" " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 20 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 21 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . lower ( ) ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 22 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( USER_NAME . upper ( ) ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 23 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( UPN ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 24 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( UPN . lower ( ) ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 25 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
self . check_digest ( UPN . upper ( ) ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 26 - 1 ] . hash )
2017-05-22 09:49:17 +12:00
name = " %s \\ %s " % ( self . netbios_domain , USER_NAME )
2017-03-31 15:50:31 +13:00
self . check_digest ( name ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 27 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
2017-05-22 09:49:17 +12:00
name = " %s \\ %s " % ( self . netbios_domain . lower ( ) , USER_NAME . lower ( ) )
2017-03-31 15:50:31 +13:00
self . check_digest ( name ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 28 - 1 ] . hash )
2017-03-31 15:50:31 +13:00
2017-05-22 09:49:17 +12:00
name = " %s \\ %s " % ( self . netbios_domain . upper ( ) , USER_NAME . upper ( ) )
2017-03-31 15:50:31 +13:00
self . check_digest ( name ,
" Digest " ,
USER_PASS ,
2018-07-30 18:18:25 +12:00
digests . hashes [ 29 - 1 ] . hash )
2017-04-12 09:12:56 +12:00
def checkUserPassword ( self , up , expected ) :
# Check we've received the correct number of hashes
2020-02-07 11:02:38 +13:00
self . assertEqual ( len ( expected ) , up . num_hashes )
2017-04-12 09:12:56 +12:00
i = 0
for ( tag , alg , rounds ) in expected :
2020-02-07 11:02:38 +13:00
self . assertEqual ( tag , up . hashes [ i ] . scheme )
2017-04-12 09:12:56 +12:00
2018-05-09 11:24:48 +01:00
data = up . hashes [ i ] . value . decode ( ' utf8 ' ) . split ( " $ " )
2017-04-12 09:12:56 +12:00
# Check we got the expected crypt algorithm
2020-02-07 11:02:38 +13:00
self . assertEqual ( alg , data [ 1 ] )
2017-04-12 09:12:56 +12:00
if rounds is None :
cmd = " $ %s $ %s " % ( alg , data [ 2 ] )
else :
cmd = " $ %s $rounds= %d $ %s " % ( alg , rounds , data [ 3 ] )
# Calculate the expected hash value
expected = crypt . crypt ( USER_PASS , cmd )
2020-02-07 11:02:38 +13:00
self . assertEqual ( expected , up . hashes [ i ] . value . decode ( ' utf8 ' ) )
2017-04-12 09:12:56 +12:00
i + = 1
# Check that the correct nt_hash was stored for userPassword
def checkNtHash ( self , password , nt_hash ) :
creds = Credentials ( )
creds . set_anonymous ( )
creds . set_password ( password )
expected = creds . get_nt_hash ( )
actual = bytearray ( nt_hash )
2020-02-07 11:02:38 +13:00
self . assertEqual ( expected , actual )