1# (c) 2018, Matthias Fuchs <matthias.s.fuchs@gmail.com> 2# 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17 18from __future__ import (absolute_import, division, print_function) 19__metaclass__ = type 20 21import sys 22 23import pytest 24 25from ansible.errors import AnsibleError, AnsibleFilterError 26from ansible.plugins.filter.core import get_encrypted_password 27from ansible.utils import encrypt 28 29 30class passlib_off(object): 31 def __init__(self): 32 self.orig = encrypt.PASSLIB_AVAILABLE 33 34 def __enter__(self): 35 encrypt.PASSLIB_AVAILABLE = False 36 return self 37 38 def __exit__(self, exception_type, exception_value, traceback): 39 encrypt.PASSLIB_AVAILABLE = self.orig 40 41 42def assert_hash(expected, secret, algorithm, **settings): 43 44 if encrypt.PASSLIB_AVAILABLE: 45 assert encrypt.passlib_or_crypt(secret, algorithm, **settings) == expected 46 assert encrypt.PasslibHash(algorithm).hash(secret, **settings) == expected 47 else: 48 assert encrypt.passlib_or_crypt(secret, algorithm, **settings) == expected 49 with pytest.raises(AnsibleError) as excinfo: 50 encrypt.PasslibHash(algorithm).hash(secret, **settings) 51 assert excinfo.value.args[0] == "passlib must be installed and usable to hash with '%s'" % algorithm 52 53 54@pytest.mark.skipif(sys.platform.startswith('darwin'), reason='macOS requires passlib') 55def test_encrypt_with_rounds_no_passlib(): 56 with passlib_off(): 57 assert_hash("$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7", 58 secret="123", algorithm="sha256_crypt", salt="12345678", rounds=5000) 59 assert_hash("$5$rounds=10000$12345678$JBinliYMFEcBeAXKZnLjenhgEhTmJBvZn3aR8l70Oy/", 60 secret="123", algorithm="sha256_crypt", salt="12345678", rounds=10000) 61 assert_hash("$6$12345678$LcV9LQiaPekQxZ.OfkMADjFdSO2k9zfbDQrHPVcYjSLqSdjLYpsgqviYvTEP/R41yPmhH3CCeEDqVhW1VHr3L.", 62 secret="123", algorithm="sha512_crypt", salt="12345678", rounds=5000) 63 64 65# If passlib is not installed. this is identical to the test_encrypt_with_rounds_no_passlib() test 66@pytest.mark.skipif(not encrypt.PASSLIB_AVAILABLE, reason='passlib must be installed to run this test') 67def test_encrypt_with_rounds(): 68 assert_hash("$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7", 69 secret="123", algorithm="sha256_crypt", salt="12345678", rounds=5000) 70 assert_hash("$5$rounds=10000$12345678$JBinliYMFEcBeAXKZnLjenhgEhTmJBvZn3aR8l70Oy/", 71 secret="123", algorithm="sha256_crypt", salt="12345678", rounds=10000) 72 assert_hash("$6$12345678$LcV9LQiaPekQxZ.OfkMADjFdSO2k9zfbDQrHPVcYjSLqSdjLYpsgqviYvTEP/R41yPmhH3CCeEDqVhW1VHr3L.", 73 secret="123", algorithm="sha512_crypt", salt="12345678", rounds=5000) 74 75 76@pytest.mark.skipif(sys.platform.startswith('darwin'), reason='macOS requires passlib') 77def test_encrypt_default_rounds_no_passlib(): 78 with passlib_off(): 79 assert_hash("$1$12345678$tRy4cXc3kmcfRZVj4iFXr/", 80 secret="123", algorithm="md5_crypt", salt="12345678") 81 assert_hash("$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7", 82 secret="123", algorithm="sha256_crypt", salt="12345678") 83 assert_hash("$6$12345678$LcV9LQiaPekQxZ.OfkMADjFdSO2k9zfbDQrHPVcYjSLqSdjLYpsgqviYvTEP/R41yPmhH3CCeEDqVhW1VHr3L.", 84 secret="123", algorithm="sha512_crypt", salt="12345678") 85 86 assert encrypt.CryptHash("md5_crypt").hash("123") 87 88 89# If passlib is not installed. this is identical to the test_encrypt_default_rounds_no_passlib() test 90@pytest.mark.skipif(not encrypt.PASSLIB_AVAILABLE, reason='passlib must be installed to run this test') 91def test_encrypt_default_rounds(): 92 assert_hash("$1$12345678$tRy4cXc3kmcfRZVj4iFXr/", 93 secret="123", algorithm="md5_crypt", salt="12345678") 94 assert_hash("$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7", 95 secret="123", algorithm="sha256_crypt", salt="12345678") 96 assert_hash("$6$12345678$LcV9LQiaPekQxZ.OfkMADjFdSO2k9zfbDQrHPVcYjSLqSdjLYpsgqviYvTEP/R41yPmhH3CCeEDqVhW1VHr3L.", 97 secret="123", algorithm="sha512_crypt", salt="12345678") 98 99 assert encrypt.PasslibHash("md5_crypt").hash("123") 100 101 102@pytest.mark.skipif(sys.platform.startswith('darwin'), reason='macOS requires passlib') 103def test_password_hash_filter_no_passlib(): 104 with passlib_off(): 105 assert not encrypt.PASSLIB_AVAILABLE 106 assert get_encrypted_password("123", "md5", salt="12345678") == "$1$12345678$tRy4cXc3kmcfRZVj4iFXr/" 107 108 with pytest.raises(AnsibleFilterError): 109 get_encrypted_password("123", "crypt16", salt="12") 110 111 112def test_password_hash_filter_passlib(): 113 if not encrypt.PASSLIB_AVAILABLE: 114 pytest.skip("passlib not available") 115 116 with pytest.raises(AnsibleFilterError): 117 get_encrypted_password("123", "sha257", salt="12345678") 118 119 # Uses 5000 rounds by default for sha256 matching crypt behaviour 120 assert get_encrypted_password("123", "sha256", salt="12345678") == "$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7" 121 assert get_encrypted_password("123", "sha256", salt="12345678", rounds=5000) == "$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7" 122 123 assert (get_encrypted_password("123", "sha256", salt="12345678", rounds=10000) == 124 "$5$rounds=10000$12345678$JBinliYMFEcBeAXKZnLjenhgEhTmJBvZn3aR8l70Oy/") 125 126 assert (get_encrypted_password("123", "sha512", salt="12345678", rounds=6000) == 127 "$6$rounds=6000$12345678$l/fC67BdJwZrJ7qneKGP1b6PcatfBr0dI7W6JLBrsv8P1wnv/0pu4WJsWq5p6WiXgZ2gt9Aoir3MeORJxg4.Z/") 128 129 assert (get_encrypted_password("123", "sha512", salt="12345678", rounds=5000) == 130 "$6$12345678$LcV9LQiaPekQxZ.OfkMADjFdSO2k9zfbDQrHPVcYjSLqSdjLYpsgqviYvTEP/R41yPmhH3CCeEDqVhW1VHr3L.") 131 132 assert get_encrypted_password("123", "crypt16", salt="12") == "12pELHK2ME3McUFlHxel6uMM" 133 134 # Try algorithm that uses a raw salt 135 assert get_encrypted_password("123", "pbkdf2_sha256") 136 137 138@pytest.mark.skipif(sys.platform.startswith('darwin'), reason='macOS requires passlib') 139def test_do_encrypt_no_passlib(): 140 with passlib_off(): 141 assert not encrypt.PASSLIB_AVAILABLE 142 assert encrypt.do_encrypt("123", "md5_crypt", salt="12345678") == "$1$12345678$tRy4cXc3kmcfRZVj4iFXr/" 143 144 with pytest.raises(AnsibleError): 145 encrypt.do_encrypt("123", "crypt16", salt="12") 146 147 148def test_do_encrypt_passlib(): 149 if not encrypt.PASSLIB_AVAILABLE: 150 pytest.skip("passlib not available") 151 152 with pytest.raises(AnsibleError): 153 encrypt.do_encrypt("123", "sha257_crypt", salt="12345678") 154 155 # Uses 5000 rounds by default for sha256 matching crypt behaviour. 156 assert encrypt.do_encrypt("123", "sha256_crypt", salt="12345678") == "$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7" 157 158 assert encrypt.do_encrypt("123", "md5_crypt", salt="12345678") == "$1$12345678$tRy4cXc3kmcfRZVj4iFXr/" 159 160 assert encrypt.do_encrypt("123", "crypt16", salt="12") == "12pELHK2ME3McUFlHxel6uMM" 161 162 163def test_random_salt(): 164 res = encrypt.random_salt() 165 expected_salt_candidate_chars = u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./' 166 assert len(res) == 8 167 for res_char in res: 168 assert res_char in expected_salt_candidate_chars 169