1import os
2import shutil
3import tempfile
4
5import pytest
6import salt.utils.files
7import salt.utils.stringutils
8from salt import crypt
9from tests.support.mock import MagicMock, MockCall, mock_open, patch
10from tests.support.unit import TestCase, skipIf
11
12try:
13    import M2Crypto
14
15    HAS_M2 = True
16except ImportError:
17    HAS_M2 = False
18try:
19    from Cryptodome.PublicKey import RSA
20
21    HAS_PYCRYPTO_RSA = True
22except ImportError:
23    HAS_PYCRYPTO_RSA = False
24if not HAS_PYCRYPTO_RSA:
25    try:
26        from Crypto.PublicKey import RSA  # nosec
27
28        HAS_PYCRYPTO_RSA = True
29    except ImportError:
30        HAS_PYCRYPTO_RSA = False
31
32
33PRIVKEY_DATA = (
34    "-----BEGIN RSA PRIVATE KEY-----\n"
35    "MIIEpAIBAAKCAQEA75GR6ZTv5JOv90Vq8tKhKC7YQnhDIo2hM0HVziTEk5R4UQBW\n"
36    "a0CKytFMbTONY2msEDwX9iA0x7F5Lgj0X8eD4ZMsYqLzqjWMekLC8bjhxc+EuPo9\n"
37    "Dygu3mJ2VgRC7XhlFpmdo5NN8J2E7B/CNB3R4hOcMMZNZdi0xLtFoTfwU61UPfFX\n"
38    "14mV2laqLbvDEfQLJhUTDeFFV8EN5Z4H1ttLP3sMXJvc3EvM0JiDVj4l1TWFUHHz\n"
39    "eFgCA1Im0lv8i7PFrgW7nyMfK9uDSsUmIp7k6ai4tVzwkTmV5PsriP1ju88Lo3MB\n"
40    "4/sUmDv/JmlZ9YyzTO3Po8Uz3Aeq9HJWyBWHAQIDAQABAoIBAGOzBzBYZUWRGOgl\n"
41    "IY8QjTT12dY/ymC05GM6gMobjxuD7FZ5d32HDLu/QrknfS3kKlFPUQGDAbQhbbb0\n"
42    "zw6VL5NO9mfOPO2W/3FaG1sRgBQcerWonoSSSn8OJwVBHMFLG3a+U1Zh1UvPoiPK\n"
43    "S734swIM+zFpNYivGPvOm/muF/waFf8tF/47t1cwt/JGXYQnkG/P7z0vp47Irpsb\n"
44    "Yjw7vPe4BnbY6SppSxscW3KoV7GtJLFKIxAXbxsuJMF/rYe3O3w2VKJ1Sug1VDJl\n"
45    "/GytwAkSUer84WwP2b07Wn4c5pCnmLslMgXCLkENgi1NnJMhYVOnckxGDZk54hqP\n"
46    "9RbLnkkCgYEA/yKuWEvgdzYRYkqpzB0l9ka7Y00CV4Dha9Of6GjQi9i4VCJ/UFVr\n"
47    "UlhTo5y0ZzpcDAPcoZf5CFZsD90a/BpQ3YTtdln2MMCL/Kr3QFmetkmDrt+3wYnX\n"
48    "sKESfsa2nZdOATRpl1antpwyD4RzsAeOPwBiACj4fkq5iZJBSI0bxrMCgYEA8GFi\n"
49    "qAjgKh81/Uai6KWTOW2kX02LEMVRrnZLQ9VPPLGid4KZDDk1/dEfxjjkcyOxX1Ux\n"
50    "Klu4W8ZEdZyzPcJrfk7PdopfGOfrhWzkREK9C40H7ou/1jUecq/STPfSOmxh3Y+D\n"
51    "ifMNO6z4sQAHx8VaHaxVsJ7SGR/spr0pkZL+NXsCgYEA84rIgBKWB1W+TGRXJzdf\n"
52    "yHIGaCjXpm2pQMN3LmP3RrcuZWm0vBt94dHcrR5l+u/zc6iwEDTAjJvqdU4rdyEr\n"
53    "tfkwr7v6TNlQB3WvpWanIPyVzfVSNFX/ZWSsAgZvxYjr9ixw6vzWBXOeOb/Gqu7b\n"
54    "cvpLkjmJ0wxDhbXtyXKhZA8CgYBZyvcQb+hUs732M4mtQBSD0kohc5TsGdlOQ1AQ\n"
55    "McFcmbpnzDghkclyW8jzwdLMk9uxEeDAwuxWE/UEvhlSi6qdzxC+Zifp5NBc0fVe\n"
56    "7lMx2mfJGxj5CnSqQLVdHQHB4zSXkAGB6XHbBd0MOUeuvzDPfs2voVQ4IG3FR0oc\n"
57    "3/znuwKBgQChZGH3McQcxmLA28aUwOVbWssfXKdDCsiJO+PEXXlL0maO3SbnFn+Q\n"
58    "Tyf8oHI5cdP7AbwDSx9bUfRPjg9dKKmATBFr2bn216pjGxK0OjYOCntFTVr0psRB\n"
59    "CrKg52Qrq71/2l4V2NLQZU40Dr1bN9V+Ftd9L0pvpCAEAWpIbLXGDw==\n"
60    "-----END RSA PRIVATE KEY-----"
61)
62
63PUBKEY_DATA = (
64    "-----BEGIN PUBLIC KEY-----\n"
65    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA75GR6ZTv5JOv90Vq8tKh\n"
66    "KC7YQnhDIo2hM0HVziTEk5R4UQBWa0CKytFMbTONY2msEDwX9iA0x7F5Lgj0X8eD\n"
67    "4ZMsYqLzqjWMekLC8bjhxc+EuPo9Dygu3mJ2VgRC7XhlFpmdo5NN8J2E7B/CNB3R\n"
68    "4hOcMMZNZdi0xLtFoTfwU61UPfFX14mV2laqLbvDEfQLJhUTDeFFV8EN5Z4H1ttL\n"
69    "P3sMXJvc3EvM0JiDVj4l1TWFUHHzeFgCA1Im0lv8i7PFrgW7nyMfK9uDSsUmIp7k\n"
70    "6ai4tVzwkTmV5PsriP1ju88Lo3MB4/sUmDv/JmlZ9YyzTO3Po8Uz3Aeq9HJWyBWH\n"
71    "AQIDAQAB\n"
72    "-----END PUBLIC KEY-----"
73)
74
75MSG = b"It's me, Mario"
76
77SIG = (
78    b"\x07\xf3\xb1\xe7\xdb\x06\xf4_\xe2\xdc\xcb!F\xfb\xbex{W\x1d\xe4E"
79    b"\xd3\r\xc5\x90\xca(\x05\x1d\x99\x8b\x1aug\x9f\x95>\x94\x7f\xe3+"
80    b"\x12\xfa\x9c\xd4\xb8\x02]\x0e\xa5\xa3LL\xc3\xa2\x8f+\x83Z\x1b\x17"
81    b'\xbfT\xd3\xc7\xfd\x0b\xf4\xd7J\xfe^\x86q"I\xa3x\xbc\xd3$\xe9M<\xe1'
82    b"\x07\xad\xf2_\x9f\xfa\xf7g(~\xd8\xf5\xe7\xda-\xa3Ko\xfc.\x99\xcf"
83    b"\x9b\xb9\xc1U\x97\x82'\xcb\xc6\x08\xaa\xa0\xe4\xd0\xc1+\xfc\x86"
84    b'\r\xe4y\xb1#\xd3\x1dS\x96D28\xc4\xd5\r\xd4\x98\x1a44"\xd7\xc2\xb4'
85    b"]\xa7\x0f\xa7Db\x85G\x8c\xd6\x94!\x8af1O\xf6g\xd7\x03\xfd\xb3\xbc"
86    b"\xce\x9f\xe7\x015\xb8\x1d]AHK\xa0\x14m\xda=O\xa7\xde\xf2\xff\x9b"
87    b"\x8e\x83\xc8j\x11\x1a\x98\x85\xde\xc5\x91\x07\x84!\x12^4\xcb\xa8"
88    b"\x98\x8a\x8a&#\xb9(#?\x80\x15\x9eW\xb5\x12\xd1\x95S\xf2<G\xeb\xf1"
89    b"\x14H\xb2\xc4>\xc3A\xed\x86x~\xcfU\xd5Q\xfe~\x10\xd2\x9b"
90)
91
92
93@skipIf(not HAS_PYCRYPTO_RSA, "pycrypto >= 2.6 is not available")
94@skipIf(HAS_M2, "m2crypto is used by salt.crypt if installed")
95class CryptTestCase(TestCase):
96    @pytest.mark.slow_test
97    def test_gen_keys(self):
98        open_priv_wb = MockCall("/keydir{}keyname.pem".format(os.sep), "wb+")
99        open_pub_wb = MockCall("/keydir{}keyname.pub".format(os.sep), "wb+")
100
101        with patch.multiple(
102            os,
103            umask=MagicMock(),
104            chmod=MagicMock(),
105            access=MagicMock(return_value=True),
106        ):
107            with patch("salt.utils.files.fopen", mock_open()) as m_open, patch(
108                "os.path.isfile", return_value=True
109            ):
110                result = crypt.gen_keys("/keydir", "keyname", 2048)
111                assert result == "/keydir{}keyname.pem".format(os.sep), result
112                assert open_priv_wb not in m_open.calls
113                assert open_pub_wb not in m_open.calls
114
115            with patch("salt.utils.files.fopen", mock_open()) as m_open, patch(
116                "os.path.isfile", return_value=False
117            ):
118                crypt.gen_keys("/keydir", "keyname", 2048)
119                assert open_priv_wb in m_open.calls
120                assert open_pub_wb in m_open.calls
121
122    @patch("os.umask", MagicMock())
123    @patch("os.chmod", MagicMock())
124    @patch("os.chown", MagicMock(), create=True)
125    @patch("os.access", MagicMock(return_value=True))
126    @pytest.mark.slow_test
127    def test_gen_keys_with_passphrase(self):
128        key_path = os.path.join(os.sep, "keydir")
129        open_priv_wb = MockCall(os.path.join(key_path, "keyname.pem"), "wb+")
130        open_pub_wb = MockCall(os.path.join(key_path, "keyname.pub"), "wb+")
131
132        with patch("salt.utils.files.fopen", mock_open()) as m_open, patch(
133            "os.path.isfile", return_value=True
134        ):
135            self.assertEqual(
136                crypt.gen_keys(key_path, "keyname", 2048, passphrase="password"),
137                os.path.join(key_path, "keyname.pem"),
138            )
139            result = crypt.gen_keys(key_path, "keyname", 2048, passphrase="password")
140            assert result == os.path.join(key_path, "keyname.pem"), result
141            assert open_priv_wb not in m_open.calls
142            assert open_pub_wb not in m_open.calls
143
144        with patch("salt.utils.files.fopen", mock_open()) as m_open, patch(
145            "os.path.isfile", return_value=False
146        ):
147            crypt.gen_keys(key_path, "keyname", 2048)
148            assert open_priv_wb in m_open.calls
149            assert open_pub_wb in m_open.calls
150
151    def test_sign_message(self):
152        key = RSA.importKey(PRIVKEY_DATA)
153        with patch("salt.crypt.get_rsa_key", return_value=key):
154            self.assertEqual(SIG, salt.crypt.sign_message("/keydir/keyname.pem", MSG))
155
156    def test_sign_message_with_passphrase(self):
157        key = RSA.importKey(PRIVKEY_DATA)
158        with patch("salt.crypt.get_rsa_key", return_value=key):
159            self.assertEqual(
160                SIG,
161                crypt.sign_message("/keydir/keyname.pem", MSG, passphrase="password"),
162            )
163
164    def test_verify_signature(self):
165        with patch("salt.utils.files.fopen", mock_open(read_data=PUBKEY_DATA)):
166            self.assertTrue(crypt.verify_signature("/keydir/keyname.pub", MSG, SIG))
167
168
169@skipIf(not HAS_M2, "m2crypto is not available")
170class M2CryptTestCase(TestCase):
171    @patch("os.umask", MagicMock())
172    @patch("os.chmod", MagicMock())
173    @patch("os.access", MagicMock(return_value=True))
174    @pytest.mark.slow_test
175    def test_gen_keys(self):
176        with patch("M2Crypto.RSA.RSA.save_pem", MagicMock()) as save_pem:
177            with patch("M2Crypto.RSA.RSA.save_pub_key", MagicMock()) as save_pub:
178                with patch("os.path.isfile", return_value=True):
179                    self.assertEqual(
180                        crypt.gen_keys("/keydir", "keyname", 2048),
181                        "/keydir{}keyname.pem".format(os.sep),
182                    )
183                    save_pem.assert_not_called()
184                    save_pub.assert_not_called()
185
186                with patch("os.path.isfile", return_value=False):
187                    self.assertEqual(
188                        crypt.gen_keys("/keydir", "keyname", 2048),
189                        "/keydir{}keyname.pem".format(os.sep),
190                    )
191                    save_pem.assert_called_once_with(
192                        "/keydir{}keyname.pem".format(os.sep), cipher=None
193                    )
194                    save_pub.assert_called_once_with(
195                        "/keydir{}keyname.pub".format(os.sep)
196                    )
197
198    @patch("os.umask", MagicMock())
199    @patch("os.chmod", MagicMock())
200    @patch("os.chown", MagicMock())
201    @patch("os.access", MagicMock(return_value=True))
202    @pytest.mark.slow_test
203    def test_gen_keys_with_passphrase(self):
204        with patch("M2Crypto.RSA.RSA.save_pem", MagicMock()) as save_pem:
205            with patch("M2Crypto.RSA.RSA.save_pub_key", MagicMock()) as save_pub:
206                with patch("os.path.isfile", return_value=True):
207                    self.assertEqual(
208                        crypt.gen_keys(
209                            "/keydir", "keyname", 2048, passphrase="password"
210                        ),
211                        "/keydir{}keyname.pem".format(os.sep),
212                    )
213                    save_pem.assert_not_called()
214                    save_pub.assert_not_called()
215
216                with patch("os.path.isfile", return_value=False):
217                    self.assertEqual(
218                        crypt.gen_keys(
219                            "/keydir", "keyname", 2048, passphrase="password"
220                        ),
221                        "/keydir{}keyname.pem".format(os.sep),
222                    )
223                    callback = save_pem.call_args[1]["callback"]
224                    save_pem.assert_called_once_with(
225                        "/keydir{}keyname.pem".format(os.sep),
226                        cipher="des_ede3_cbc",
227                        callback=callback,
228                    )
229                    self.assertEqual(callback(None), b"password")
230                    save_pub.assert_called_once_with(
231                        "/keydir{}keyname.pub".format(os.sep)
232                    )
233
234    def test_sign_message(self):
235        key = M2Crypto.RSA.load_key_string(
236            salt.utils.stringutils.to_bytes(PRIVKEY_DATA)
237        )
238        with patch("salt.crypt.get_rsa_key", return_value=key):
239            self.assertEqual(SIG, salt.crypt.sign_message("/keydir/keyname.pem", MSG))
240
241    def test_sign_message_with_passphrase(self):
242        key = M2Crypto.RSA.load_key_string(
243            salt.utils.stringutils.to_bytes(PRIVKEY_DATA)
244        )
245        with patch("salt.crypt.get_rsa_key", return_value=key):
246            self.assertEqual(
247                SIG,
248                crypt.sign_message("/keydir/keyname.pem", MSG, passphrase="password"),
249            )
250
251    def test_verify_signature(self):
252        with patch(
253            "salt.utils.files.fopen",
254            mock_open(read_data=salt.utils.stringutils.to_bytes(PUBKEY_DATA)),
255        ):
256            self.assertTrue(crypt.verify_signature("/keydir/keyname.pub", MSG, SIG))
257
258    def test_encrypt_decrypt_bin(self):
259        priv_key = M2Crypto.RSA.load_key_string(
260            salt.utils.stringutils.to_bytes(PRIVKEY_DATA)
261        )
262        pub_key = M2Crypto.RSA.load_pub_key_bio(
263            M2Crypto.BIO.MemoryBuffer(salt.utils.stringutils.to_bytes(PUBKEY_DATA))
264        )
265        encrypted = salt.crypt.private_encrypt(priv_key, b"salt")
266        decrypted = salt.crypt.public_decrypt(pub_key, encrypted)
267        self.assertEqual(b"salt", decrypted)
268
269
270class TestBadCryptodomePubKey(TestCase):
271    """
272    Test that we can load public keys exported by pycrpytodome<=3.4.6
273    """
274
275    TEST_KEY = (
276        "-----BEGIN RSA PUBLIC KEY-----\n"
277        "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzLtFhsvfbFDFaUgulSEX\n"
278        "Gl12XriL1DT78Ef2/u8HHaSMmPie37BLWas/zaHwI6066bIyYQJ/nUCahTaoHM7L\n"
279        "GlWc0wOU6zyfpihCRQHil05Y6F+olFBoZuYbFPvtp7/hJx/D7I/0n2o/c7M5i3Y2\n"
280        "3sBxAYNooIQHXHUmPQW6C9iu95ylZDW8JQzYy/EI4vCC8yQMdTK8jK1FQV0Sbwny\n"
281        "qcMxSyAWDoFbnhh2P2TnO8HOWuUOaXR8ZHOJzVcDl+a6ew+medW090x3K5O1f80D\n"
282        "+WjgnG6b2HG7VQpOCfM2GALD/FrxicPilvZ38X1aLhJuwjmVE4LAAv8DVNJXohaO\n"
283        "WQIDAQAB\n"
284        "-----END RSA PUBLIC KEY-----\n"
285    )
286
287    def setUp(self):
288        self.test_dir = tempfile.mkdtemp()
289        self.key_path = os.path.join(self.test_dir, "cryptodom-3.4.6.pub")
290        with salt.utils.files.fopen(self.key_path, "wb") as fd:
291            fd.write(self.TEST_KEY.encode())
292
293    def tearDown(self):
294        shutil.rmtree(self.test_dir)
295
296    @skipIf(not HAS_M2, "Skip when m2crypto is not installed")
297    def test_m2_bad_key(self):
298        """
299        Load public key with an invalid header using m2crypto and validate it
300        """
301        key = salt.crypt.get_rsa_pub_key(self.key_path)
302        assert key.check_key() == 1
303
304    @skipIf(HAS_M2, "Skip when m2crypto is installed")
305    def test_crypto_bad_key(self):
306        """
307        Load public key with an invalid header and validate it without m2crypto
308        """
309        key = salt.crypt.get_rsa_pub_key(self.key_path)
310        assert key.can_encrypt()
311
312
313class TestM2CryptoRegression47124(TestCase):
314
315    SIGNATURE = (
316        b"w\xac\xfe18o\xeb\xfb\x14+\x9e\xd1\xb7\x7fe}\xec\xd6\xe1P\x9e\xab"
317        b"\xb5\x07\xe0\xc1\xfd\xda#\x04Z\x8d\x7f\x0b\x1f}:~\xb2s\x860u\x02N"
318        b'\xd4q"\xb7\x86*\x8f\x1f\xd0\x9d\x11\x92\xc5~\xa68\xac>\x12H\xc2%y,'
319        b"\xe6\xceU\x1e\xa3?\x0c,\xf0u\xbb\xd0[g_\xdd\x8b\xb0\x95:Y\x18\xa5*"
320        b"\x99\xfd\xf3K\x92\x92 ({\xd1\xff\xd9F\xc8\xd6K\x86e\xf9\xa8\xad\xb0z"
321        b"\xe3\x9dD\xf5k\x8b_<\xe7\xe7\xec\xf3\"'\xd5\xd2M\xb4\xce\x1a\xe3$"
322        b"\x9c\x81\xad\xf9\x11\xf6\xf5>)\xc7\xdd\x03&\xf7\x86@ks\xa6\x05\xc2"
323        b"\xd0\xbd\x1a7\xfc\xde\xe6\xb0\xad!\x12#\xc86Y\xea\xc5\xe3\xe2\xb3"
324        b"\xc9\xaf\xfa\x0c\xf2?\xbf\x93w\x18\x9e\x0b\xa2a\x10:M\x05\x89\xe2W.Q"
325        b"\xe8;yGT\xb1\xf2\xc6A\xd2\xc4\xbeN\xb3\xcfS\xaf\x03f\xe2\xb4)\xe7\xf6"
326        b'\xdbs\xd0Z}8\xa4\xd2\x1fW*\xe6\x1c"\x8b\xd0\x18w\xb9\x7f\x9e\x96\xa3'
327        b"\xd9v\xf7\x833\x8e\x01"
328    )
329
330    @skipIf(not HAS_M2, "Skip when m2crypto is not installed")
331    def test_m2crypto_verify_bytes(self):
332        message = salt.utils.stringutils.to_unicode("meh")
333        with patch(
334            "salt.utils.files.fopen",
335            mock_open(read_data=salt.utils.stringutils.to_bytes(PUBKEY_DATA)),
336        ):
337            salt.crypt.verify_signature("/keydir/keyname.pub", message, self.SIGNATURE)
338
339    @skipIf(not HAS_M2, "Skip when m2crypto is not installed")
340    def test_m2crypto_verify_unicode(self):
341        message = salt.utils.stringutils.to_bytes("meh")
342        with patch(
343            "salt.utils.files.fopen",
344            mock_open(read_data=salt.utils.stringutils.to_bytes(PUBKEY_DATA)),
345        ):
346            salt.crypt.verify_signature("/keydir/keyname.pub", message, self.SIGNATURE)
347
348    @skipIf(not HAS_M2, "Skip when m2crypto is not installed")
349    def test_m2crypto_sign_bytes(self):
350        message = salt.utils.stringutils.to_unicode("meh")
351        key = M2Crypto.RSA.load_key_string(
352            salt.utils.stringutils.to_bytes(PRIVKEY_DATA)
353        )
354        with patch("salt.crypt.get_rsa_key", return_value=key):
355            signature = salt.crypt.sign_message(
356                "/keydir/keyname.pem", message, passphrase="password"
357            )
358        self.assertEqual(signature, self.SIGNATURE)
359
360    @skipIf(not HAS_M2, "Skip when m2crypto is not installed")
361    def test_m2crypto_sign_unicode(self):
362        message = salt.utils.stringutils.to_bytes("meh")
363        key = M2Crypto.RSA.load_key_string(
364            salt.utils.stringutils.to_bytes(PRIVKEY_DATA)
365        )
366        with patch("salt.crypt.get_rsa_key", return_value=key):
367            signature = salt.crypt.sign_message(
368                "/keydir/keyname.pem", message, passphrase="password"
369            )
370        self.assertEqual(signature, self.SIGNATURE)
371
372
373@skipIf(
374    not HAS_M2 and not HAS_PYCRYPTO_RSA,
375    "No crypto library found. Install either M2Crypto or Cryptodome to run this test",
376)
377class TestCrypt(TestCase):
378    def test_pwdata_decrypt(self):
379        key_string = """-----BEGIN RSA PRIVATE KEY-----
380MIIEpQIBAAKCAQEAzhBRyyHa7b63RLE71uKMKgrpulcAJjaIaN68ltXcCvy4w9pi
381Kj+4I3Qp6RvUaHOEmymqyjOMjQc6iwpe0scCFqh3nUk5YYaLZ3WAW0htQVlnesgB
382ZiBg9PBeTQY/LzqtudL6RCng/AX+fbnCsddlIysRxnUoNVMvz0gAmCY2mnTDjcTt
383pyxuk2T0AHSHNCKCalm75L1bWDFF+UzFemf536tBfBUGRWR6jWTij85vvCntxHS/
384HdknaTJ50E7XGVzwBJpCyV4Y2VXuW/3KrCNTqXw+jTmEw0vlcshfDg/vb3IxsUSK
3855KuHalKq/nUIc+F4QCJOl+A10goGdIfYC1/67QIDAQABAoIBAAOP+qoFWtCTZH22
386hq9PWVb8u0+yY1lFxhPyDdaZueUiu1r/coUCdv996Z+TEJgBr0AzdzVpsLtbbaKr
387ujnwoNOdc/vvISPTfKN8P4zUcrcXgZd4z7VhR+vUH/0652q8m/ZDdHorMy2IOP8Z
388cAk9DQ2PmA4TRm+tkX0G5KO8vWLsK921aRMWdsKJyQ0lYxl7M8JWupFsCJFr/U+8
389dAVtwnUiS7RnhBABZ1cfNTHYhXVAh4d+a9y/gZ00a66OGqPxiXfhjjDUZ6fGvWKN
390FlhKWEg6YqIx/H4aNXkLI5Rzzhdx/c2ukNm7+X2veRcAW7bcTwk8wxJxciEP5pBi
3911el9VE0CgYEA/lbzdE2M4yRBvTfYYC6BqZcn+BqtrAUc2h3fEy+p7lwlet0af1id
392gWpYpOJyLc0AUfR616/m2y3PwEH/nMKDSTuU7o/qKNtlHW0nQcnhDCjTUydS3+J/
393JM3dhfgVqi03rjqNcgHA2eOEwcu/OBZtiaC0wqKbuRZRtfGffyoO3ssCgYEAz2iw
394wqu/NkA+MdQIxz/a3Is7gGwoFu6h7O+XU2uN8Y2++jSBw9AzzWj31YCvyjuJPAE+
395gxHm6yOnNoLVn423NtibHejhabzHNIK6UImH99bSTKabsxfF2BX6v982BimU1jwc
396bYykzws37oN/poPb5FTpEiAUrsd2bAMn/1S43icCgYEAulHkY0z0aumCpyUkA8HO
397BvjOtPiGRcAxFLBRXPLL3+vtIQachLHcIJRRf+jLkDXfiCo7W4pm6iWzTbqLkMEG
398AD3/qowPFAM1Hct6uL01efzmYsIp+g0o60NMhvnolRQu+Bm4yM30AyqjdHzYBjSX
3995fyuru8EeSCal1j8aOHcpuUCgYEAhGhDH6Pg59NPYSQJjpm3MMA59hwV473n5Yh2
400xKyO6zwgRT6r8MPDrkhqnwQONT6Yt5PbwnT1Q/t4zhXsJnWkFwFk1U1MSeJYEa+7
401HZsPECs2CfT6xPRSO0ac00y+AmUdPT8WruDwfbSdukh8f2MCR9vlBsswKPvxH7dM
402G3aMplUCgYEAmMFgB/6Ox4OsQPPC6g4G+Ezytkc4iVkMEcjiVWzEsYATITjq3weO
403/XDGBYJoBhYwWPi9oBufFc/2pNtWy1FKKXPuVyXQATdA0mfEPbtsHjMFQNZbeKnm
4040na/SysSDCK3P+9ijlbjqLjMmPEmhJxGWTJ7khnTTkfre7/w9ZxJxi8=
405-----END RSA PRIVATE KEY-----"""
406        pwdata = b"""\
407V\x80+b\xca\x06M\xb6\x12\xc6\xe8\xf2\xb5\xbb\xd8m\xc0\x97\x9a\xeb\xb9q\x19\xc3\
408\xcdi\xb84\x90\xaf\x12kT\xe2@u\xd6\xe8T\x89\xa3\xc7\xb2Y\xd1N\x00\xa9\xc0"\xbe\
409\xed\xb1\xc3\xb7^\xbf\xbd\x8b\x13\xd3/L\x1b\xa1`\xe2\xea\x03\x98\x82\xf3uS&|\
410\xe5\xd8J\xce\xfc\x97\x8d\x0b\x949\xc0\xbd^\xef\xc6\xfd\xce\xbb\x1e\xd0"(m\xe1\
411\x95\xfb\xc8/\x07\x93\xb8\xda\x8f\x99\xfe\xdc\xd5\xcb\xdb\xb2\xf11M\xdbD\xcf\
412\x95\x13p\r\xa4\x1c{\xd5\xdb\xc7\xe5\xaf\x95F\x97\xa9\x00p~\xb5\xec\xa4.\xd0\
413\xa4\xb4\xf4f\xcds,Y/\xa1:WF\xb8\xc7\x07\xaa\x0b<\'~\x1b$D9\xd4\x8d\xf0x\xc5\
414\xee\xa8:\xe6\x00\x10\xc5i\x11\xc7]C8\x05l\x8b\x9b\xc3\x83e\xf7y\xadi:0\xb4R\
415\x1a(\x04&yL8\x19s\n\x11\x81\xfd?\xfb2\x80Ll\xa1\xdc\xc9\xb6P\xca\x8d\'\x11\xc1\
416\x07\xa5\xa1\x058\xc7\xce\xbeb\x92\xbf\x0bL\xec\xdf\xc3M\x83\xfb$\xec\xd5\xf9\
417"""
418        self.assertEqual("1234", salt.crypt.pwdata_decrypt(key_string, pwdata))
419