# -*- coding: utf-8 -*-
#
#  SelfTest/Cipher/test_pkcs1_15.py: Self-test for PKCS#1 v1.5 encryption
#
# ===================================================================
# The contents of this file are dedicated to the public domain.  To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================

from __future__ import print_function

import unittest

from Cryptodome.PublicKey import RSA
from Cryptodome.SelfTest.st_common import list_test_cases, a2b_hex
from Cryptodome import Random
from Cryptodome.Cipher import PKCS1_v1_5 as PKCS
from Cryptodome.Util.py3compat import b
from Cryptodome.Util.number import bytes_to_long, long_to_bytes
from Cryptodome.SelfTest.loader import load_test_vectors_wycheproof


def rws(t):
    """Remove white spaces, tabs, and new lines from a string"""
    for c in ['\n', '\t', ' ']:
        t = t.replace(c, '')
    return t


def t2b(t):
    """Convert a text string with bytes in hex form to a byte string"""
    clean = b(rws(t))
    if len(clean) % 2 == 1:
        raise ValueError("Even number of characters expected")
    return a2b_hex(clean)


class PKCS1_15_Tests(unittest.TestCase):

        def setUp(self):
                self.rng = Random.new().read
                self.key1024 = RSA.generate(1024, self.rng)

        # List of tuples with test data for PKCS#1 v1.5.
        # Each tuple is made up by:
        #       Item #0: dictionary with RSA key component, or key to import
        #       Item #1: plaintext
        #       Item #2: ciphertext
        #       Item #3: random data

        _testData = (

                #
                # Generated with openssl 0.9.8o
                #
                (
                # Private key
                '''-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDAiAnvIAOvqVwJTaYzsKnefZftgtXGE2hPJppGsWl78yz9jeXY
W/FxX/gTPURArNhdnhP6n3p2ZaDIBrO2zizbgIXs0IsljTTcr4vnI8fMXzyNUOjA
zP3nzMqZDZK6757XQAobOssMkBFqRWwilT/3DsBhRpl3iMUhF+wvpTSHewIDAQAB
AoGAC4HV/inOrpgTvSab8Wj0riyZgQOZ3U3ZpSlsfR8ra9Ib9Uee3jCYnKscu6Gk
y6zI/cdt8EPJ4PuwAWSNJzbpbVaDvUq25OD+CX8/uRT08yBS4J8TzBitZJTD4lS7
atdTnKT0Wmwk+u8tDbhvMKwnUHdJLcuIsycts9rwJVapUtkCQQDvDpx2JMun0YKG
uUttjmL8oJ3U0m3ZvMdVwBecA0eebZb1l2J5PvI3EJD97eKe91Nsw8T3lwpoN40k
IocSVDklAkEAzi1HLHE6EzVPOe5+Y0kGvrIYRRhncOb72vCvBZvD6wLZpQgqo6c4
d3XHFBBQWA6xcvQb5w+VVEJZzw64y25sHwJBAMYReRl6SzL0qA0wIYrYWrOt8JeQ
8mthulcWHXmqTgC6FEXP9Es5GD7/fuKl4wqLKZgIbH4nqvvGay7xXLCXD/ECQH9a
1JYNMtRen5unSAbIOxRcKkWz92F0LKpm9ZW/S9vFHO+mBcClMGoKJHiuQxLBsLbT
NtEZfSJZAeS2sUtn3/0CQDb2M2zNBTF8LlM0nxmh0k9VGm5TVIyBEMcipmvOgqIs
HKukWBcq9f/UOmS0oEhai/6g+Uf7VHJdWaeO5LzuvwU=
-----END RSA PRIVATE KEY-----''',
                # Plaintext
                '''THIS IS PLAINTEXT\x0A''',
                # Ciphertext
                '''3f dc fd 3c cd 5c 9b 12  af 65 32 e3 f7 d0 da 36
                8f 8f d9 e3 13 1c 7f c8  b3 f9 c1 08 e4 eb 79 9c
                91 89 1f 96 3b 94 77 61  99 a4 b1 ee 5d e6 17 c9
                5d 0a b5 63 52 0a eb 00  45 38 2a fb b0 71 3d 11
                f7 a1 9e a7 69 b3 af 61  c0 bb 04 5b 5d 4b 27 44
                1f 5b 97 89 ba 6a 08 95  ee 4f a2 eb 56 64 e5 0f
                da 7c f9 9a 61 61 06 62  ed a0 bc 5f aa 6c 31 78
                70 28 1a bb 98 3c e3 6a  60 3c d1 0b 0f 5a f4 75''',
                # Random data
                '''eb d7 7d 86 a4 35 23 a3 54 7e 02 0b 42 1d
                61 6c af 67 b8 4e 17 56 80 66 36 04 64 34 26 8a
                47 dd 44 b3 1a b2 17 60 f4 91 2e e2 b5 95 64 cc
                f9 da c8 70 94 54 86 4c ef 5b 08 7d 18 c4 ab 8d
                04 06 33 8f ca 15 5f 52 60 8a a1 0c f5 08 b5 4c
                bb 99 b8 94 25 04 9c e6 01 75 e6 f9 63 7a 65 61
                13 8a a7 47 77 81 ae 0d b8 2c 4d 50 a5'''
                ),
        )

        def testEncrypt1(self):
                for test in self._testData:
                        # Build the key
                        key = RSA.importKey(test[0])
                        # RNG that takes its random numbers from a pool given
                        # at initialization
                        class randGen:
                            def __init__(self, data):
                                self.data = data
                                self.idx = 0
                            def __call__(self, N):
                                r = self.data[self.idx:self.idx+N]
                                self.idx += N
                                return r
                        # The real test
                        cipher = PKCS.new(key, randfunc=randGen(t2b(test[3])))
                        ct = cipher.encrypt(b(test[1]))
                        self.assertEqual(ct, t2b(test[2]))

        def testEncrypt2(self):
                # Verify that encryption fail if plaintext is too long
                pt = '\x00'*(128-11+1)
                cipher = PKCS.new(self.key1024)
                self.assertRaises(ValueError, cipher.encrypt, pt)

        def testVerify1(self):
            for test in self._testData:
                key = RSA.importKey(test[0])
                expected_pt = b(test[1])
                ct = t2b(test[2])
                cipher = PKCS.new(key)

                # The real test
                pt = cipher.decrypt(ct, None)
                self.assertEqual(pt, expected_pt)

                pt = cipher.decrypt(ct, b'\xFF' * len(expected_pt))
                self.assertEqual(pt, expected_pt)

        def testVerify2(self):
            # Verify that decryption fails if ciphertext is not as long as
            # RSA modulus
            cipher = PKCS.new(self.key1024)
            self.assertRaises(ValueError, cipher.decrypt, '\x00'*127, "---")
            self.assertRaises(ValueError, cipher.decrypt, '\x00'*129, "---")

            # Verify that decryption fails if there are less then 8 non-zero padding
            # bytes
            pt = b('\x00\x02' + '\xFF'*7 + '\x00' + '\x45'*118)
            pt_int = bytes_to_long(pt)
            ct_int = self.key1024._encrypt(pt_int)
            ct = long_to_bytes(ct_int, 128)
            self.assertEqual(b"---", cipher.decrypt(ct, b"---"))

        def testEncryptVerify1(self):
            # Encrypt/Verify messages of length [0..RSAlen-11]
            # and therefore padding [8..117]
            for pt_len in range(0, 128 - 11 + 1):
                pt = self.rng(pt_len)
                cipher = PKCS.new(self.key1024)
                ct = cipher.encrypt(pt)
                pt2 = cipher.decrypt(ct, b'\xAA' * pt_len)
                self.assertEqual(pt, pt2)

        def test_encrypt_verify_exp_pt_len(self):

            cipher = PKCS.new(self.key1024)
            pt = b'5' * 16
            ct = cipher.encrypt(pt)
            sentinel = b'\xAA' * 16

            pt_A = cipher.decrypt(ct, sentinel, 16)
            self.assertEqual(pt, pt_A)

            pt_B = cipher.decrypt(ct, sentinel, 15)
            self.assertEqual(sentinel, pt_B)

            pt_C = cipher.decrypt(ct, sentinel, 17)
            self.assertEqual(sentinel, pt_C)

        def testByteArray(self):
            pt = b"XER"
            cipher = PKCS.new(self.key1024)
            ct = cipher.encrypt(bytearray(pt))
            pt2 = cipher.decrypt(bytearray(ct), '\xFF' * len(pt))
            self.assertEqual(pt, pt2)

        def testMemoryview(self):
            pt = b"XER"
            cipher = PKCS.new(self.key1024)
            ct = cipher.encrypt(memoryview(bytearray(pt)))
            pt2 = cipher.decrypt(memoryview(bytearray(ct)), b'\xFF' * len(pt))
            self.assertEqual(pt, pt2)

        def test_return_type(self):
            pt = b"XYZ"
            cipher = PKCS.new(self.key1024)
            ct = cipher.encrypt(pt)
            self.assertTrue(isinstance(ct, bytes))
            pt2 = cipher.decrypt(ct, b'\xAA' * 3)
            self.assertTrue(isinstance(pt2, bytes))


class TestVectorsWycheproof(unittest.TestCase):

    def __init__(self, wycheproof_warnings, skip_slow_tests):
        unittest.TestCase.__init__(self)
        self._wycheproof_warnings = wycheproof_warnings
        self._skip_slow_tests = skip_slow_tests
        self._id = "None"

    def load_tests(self, filename):

        def filter_rsa(group):
            return RSA.import_key(group['privateKeyPem'])

        result = load_test_vectors_wycheproof(("Cipher", "wycheproof"),
                                              filename,
                                              "Wycheproof PKCS#1v1.5 (%s)" % filename,
                                              group_tag={'rsa_key': filter_rsa}
                                              )
        return result

    def setUp(self):
        self.tv = []
        self.tv.extend(self.load_tests("rsa_pkcs1_2048_test.json"))
        if not self._skip_slow_tests:
            self.tv.extend(self.load_tests("rsa_pkcs1_3072_test.json"))
            self.tv.extend(self.load_tests("rsa_pkcs1_4096_test.json"))

    def shortDescription(self):
        return self._id

    def warn(self, tv):
        if tv.warning and self._wycheproof_warnings:
            import warnings
            warnings.warn("Wycheproof warning: %s (%s)" % (self._id, tv.comment))

    def test_decrypt(self, tv):
        self._id = "Wycheproof Decrypt PKCS#1v1.5 Test #%s" % tv.id
        sentinel = b'\xAA' * max(3, len(tv.msg))
        cipher = PKCS.new(tv.rsa_key)
        try:
            pt = cipher.decrypt(tv.ct, sentinel=sentinel)
        except ValueError:
            assert not tv.valid
        else:
            if pt == sentinel:
                assert not tv.valid
            else:
                assert tv.valid
                self.assertEqual(pt, tv.msg)
                self.warn(tv)

    def runTest(self):

        for tv in self.tv:
            self.test_decrypt(tv)


def get_tests(config={}):
    skip_slow_tests = not config.get('slow_tests')
    wycheproof_warnings = config.get('wycheproof_warnings')

    tests = []
    tests += list_test_cases(PKCS1_15_Tests)
    tests += [TestVectorsWycheproof(wycheproof_warnings, skip_slow_tests)]
    return tests


if __name__ == '__main__':
    def suite():
        return unittest.TestSuite(get_tests())
    unittest.main(defaultTest='suite')

# vim:set ts=4 sw=4 sts=4 expandtab:
