diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index 4dbbf5ab..495a3336 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,6 +1,9 @@ +import binascii + import six -from pyasn1.codec.der import encoder -from pyasn1.type import univ +from pyasn1.codec.der import decoder, encoder +from pyasn1.error import PyAsn1Error +from pyasn1.type import namedtype, univ import rsa as pyrsa import rsa.pem as pyrsa_pem @@ -12,7 +15,18 @@ from jose.utils import base64_to_long, long_to_base64 -PKCS8_RSA_HEADER = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00' +LEGACY_INVALID_PKCS8_RSA_HEADER = binascii.unhexlify( + "30" # sequence + "8204BD" # DER-encoded sequence contents length of 1213 bytes -- INCORRECT STATIC LENGTH + "020100" # integer: 0 -- Version + "30" # sequence + "0D" # DER-encoded sequence contents length of 13 bytes -- PrivateKeyAlgorithmIdentifier + "06092A864886F70D010101" # OID -- rsaEncryption + "0500" # NULL -- parameters +) +ASN1_SEQUENCE_ID = binascii.unhexlify("30") +RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" + # Functions gcd and rsa_recover_prime_factors were copied from cryptography 1.9 # to enable pure python rsa module to be in compliance with section 6.3.1 of RFC7518 # which requires only private exponent (d) for private key. @@ -83,6 +97,65 @@ def pem_to_spki(pem, fmt='PKCS8'): return key.to_pem(fmt) +def _legacy_private_key_pkcs8_to_pkcs1(pkcs8_key): + """Legacy RSA private key PKCS8-to-PKCS1 conversion. + + .. warning:: + + This is incorrect parsing and only works because the legacy PKCS1-to-PKCS8 + encoding was also incorrect. + """ + # Only allow this processing if the prefix matches + # AND the following byte indicates an ASN1 sequence, + # as we would expect with the legacy encoding. + if not pkcs8_key.startswith(LEGACY_INVALID_PKCS8_RSA_HEADER + ASN1_SEQUENCE_ID): + raise ValueError("Invalid private key encoding") + + return pkcs8_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER):] + + +class PKCS8RsaPrivateKeyAlgorithm(univ.Sequence): + """ASN1 structure for recording RSA PrivateKeyAlgorithm identifiers.""" + componentType = namedtype.NamedTypes( + namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()), + namedtype.NamedType("parameters", univ.Null()) + ) + + +class PKCS8PrivateKey(univ.Sequence): + """ASN1 structure for recording PKCS8 private keys.""" + componentType = namedtype.NamedTypes( + namedtype.NamedType("version", univ.Integer()), + namedtype.NamedType("privateKeyAlgorithm", PKCS8RsaPrivateKeyAlgorithm()), + namedtype.NamedType("privateKey", univ.OctetString()) + ) + + +def _private_key_pkcs8_to_pkcs1(pkcs8_key): + """Convert a PKCS8-encoded RSA private key to PKCS1.""" + decoded_values = decoder.decode(pkcs8_key, asn1Spec=PKCS8PrivateKey()) + + try: + decoded_key = decoded_values[0] + except IndexError: + raise ValueError("Invalid private key encoding") + + return decoded_key["privateKey"] + + +def _private_key_pkcs1_to_pkcs8(pkcs1_key): + """Convert a PKCS1-encoded RSA private key to PKCS8.""" + algorithm = PKCS8RsaPrivateKeyAlgorithm() + algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID + + pkcs8_key = PKCS8PrivateKey() + pkcs8_key["version"] = 0 + pkcs8_key["privateKeyAlgorithm"] = algorithm + pkcs8_key["privateKey"] = pkcs1_key + + return encoder.encode(pkcs8_key) + + class RSAKey(Key): SHA256 = 'SHA-256' SHA384 = 'SHA-384' @@ -121,12 +194,15 @@ def __init__(self, key, algorithm): self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key) except ValueError: try: - # python-rsa does not support PKCS8 yet so we have to manually remove OID der = pyrsa_pem.load_pem(key, b'PRIVATE KEY') - header, der = der[:22], der[22:] - if header != PKCS8_RSA_HEADER: - raise ValueError("Invalid PKCS8 header") - self._prepared_key = pyrsa.PrivateKey._load_pkcs1_der(der) + try: + pkcs1_key = _private_key_pkcs8_to_pkcs1(der) + except PyAsn1Error: + # If the key was encoded using the old, invalid, + # encoding then pyasn1 will throw an error attempting + # to parse the key. + pkcs1_key = _legacy_private_key_pkcs8_to_pkcs1(der) + self._prepared_key = pyrsa.PrivateKey.load_pkcs1(pkcs1_key, format="DER") except ValueError as e: raise JWKError(e) return @@ -183,7 +259,8 @@ def to_pem(self, pem_format='PKCS8'): if isinstance(self._prepared_key, pyrsa.PrivateKey): der = self._prepared_key.save_pkcs1(format='DER') if pem_format == 'PKCS8': - pem = pyrsa_pem.save_pem(PKCS8_RSA_HEADER + der, pem_marker='PRIVATE KEY') + pkcs8_der = _private_key_pkcs1_to_pkcs8(der) + pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker='PRIVATE KEY') elif pem_format == 'PKCS1': pem = pyrsa_pem.save_pem(der, pem_marker='RSA PRIVATE KEY') else: @@ -196,7 +273,7 @@ def to_pem(self, pem_format='PKCS8'): der = encoder.encode(asn_key) header = PubKeyHeader() - header['oid'] = univ.ObjectIdentifier('1.2.840.113549.1.1.1') + header['oid'] = univ.ObjectIdentifier(RSA_ENCRYPTION_ASN1_OID) pub_key = OpenSSLPubKey() pub_key['header'] = header pub_key['key'] = univ.BitString.fromOctetString(der) diff --git a/requirements.txt b/requirements.txt index 5343103b..4083b222 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ six future rsa ecdsa +pyasn1 diff --git a/setup.py b/setup.py index 63116c23..9397c06e 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,11 @@ def get_packages(package): 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'], 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'], } +legacy_backend_requires = ['ecdsa <1.0', 'rsa', 'pyasn1'] +install_requires = ['six <2.0', 'future <1.0'] + +# TODO: work this into the extras selection instead. +install_requires += legacy_backend_requires setup( @@ -64,5 +69,5 @@ def get_packages(package): 'pytest-cov', 'pytest-runner', ], - install_requires=['six <2.0', 'ecdsa <1.0', 'rsa', 'future <1.0'] + install_requires=install_requires ) diff --git a/tests/algorithms/test_EC_compat.py b/tests/algorithms/test_EC_compat.py index fd43c80d..1fc6dab3 100644 --- a/tests/algorithms/test_EC_compat.py +++ b/tests/algorithms/test_EC_compat.py @@ -15,7 +15,7 @@ None in (ECDSAECKey, CryptographyECKey), reason="Multiple crypto backends not available for backend compatibility tests" ) -class TestBackendRsaCompatibility(object): +class TestBackendEcdsaCompatibility(object): @pytest.mark.parametrize("BackendSign", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendVerify", [ECDSAECKey, CryptographyECKey]) @@ -31,3 +31,42 @@ def test_signing_parity(self, BackendSign, BackendVerify): # invalid signature assert not key_verify.verify(msg, b'n' * 64) + + @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) + @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) + def test_public_key_to_pem(self, BackendFrom, BackendTo): + key = BackendFrom(private_key, ALGORITHMS.ES256) + key2 = BackendTo(private_key, ALGORITHMS.ES256) + + assert key.public_key().to_pem().strip() == key2.public_key().to_pem().strip() + + @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) + @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) + def test_private_key_to_pem(self, BackendFrom, BackendTo): + key = BackendFrom(private_key, ALGORITHMS.ES256) + key2 = BackendTo(private_key, ALGORITHMS.ES256) + + assert key.to_pem().strip() == key2.to_pem().strip() + + @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) + @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) + def test_public_key_load_cycle(self, BackendFrom, BackendTo): + key = BackendFrom(private_key, ALGORITHMS.ES256) + pubkey = key.public_key() + + pub_pem_source = pubkey.to_pem().strip() + + pub_target = BackendTo(pub_pem_source, ALGORITHMS.ES256) + + assert pub_pem_source == pub_target.to_pem().strip() + + @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) + @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) + def test_private_key_load_cycle(self, BackendFrom, BackendTo): + key = BackendFrom(private_key, ALGORITHMS.ES256) + + pem_source = key.to_pem().strip() + + target = BackendTo(pem_source, ALGORITHMS.ES256) + + assert pem_source == target.to_pem().strip() diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index ad1cef68..944b9945 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,5 +1,12 @@ +import base64 import sys +try: + from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey + from jose.backends import rsa_backend +except ImportError: + PurePythonRSAKey = rsa_backend = None + try: from Crypto.PublicKey import RSA as PyCryptoRSA from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey @@ -24,7 +31,7 @@ if sys.version_info > (3,): long = int -private_key = b"""-----BEGIN RSA PRIVATE KEY----- +private_key_4096_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M @@ -75,6 +82,253 @@ bjJ/JfTO5060SsWftf4iw3jrhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGR mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8= -----END RSA PRIVATE KEY-----""" +private_key_2048_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAom6GcUPchmHxBuV3zJ60EPC7y30WiiVxn1WXSPHmfqaj0q2U +xS03YugkYmX9lB/EQ6Z5bOY9VuL1oMudL6Dkb9aYYEBZHVgejV7vtYuYT19QMesn +AsmGq8etie7XyWHzfWTxljbF53yvxXJMixcFzebAov9pUiV9Hmy3hYVLw3J1NXVg +gPZpUT2oF+qAayhPsOi2b0CrIE3FvioDx7IiRXKFpV/1gah3NRSKxCrsxV6V+UGO ++trP1ViWiu4oXB5j25kZmkgI0lXG60p58DUUeCOnEemvurltf9T9IEs7LGBEzUYm +itGSY4ZOY3MabPypRfFRRotZEDyZjshq4xfXAwIDAQABAoIBAFclRzoTd4gdmevi +RwDgEKmaDpchGGurpScgC5eWONywWOpaOJwFI1cMRyEHqSHEXU8STMkxSa2I/NF1 +DHMWNhkOoBfbzjPhKBse2Sqkp2XGNEdj6z0ik/8rlR6QpvMjezhGZRr7bfhBPCiJ +pylkg7exWp7Yu0/YTyV4nImlNz23GvrYHFtzDzTtn9gW4fe46wI08s4PqH/TyBh8 +QkwkTwOKTk6n/xz2hND/shUOGjaoS0o6y4+8v3O1JYUWa7YZaIFofvF/dHR0yieg +2gQjc0c6+VeBm8dEbn3he+KnIBwQbWsiCuWL6Jq4XPtMbqutfovIYf9lRB+3q2PI +VSh3mwECgYEAzhOhG+usoxjJGk2wVJH5wnHL0zyH8gWF4SnnxwwdBOF4kdLB2eva +SJsi8rJQMT0TC4wZ6TsD2fJXGazIyM6OnD+52AViiUsLVS5MR7qEMNitdkWEtDx9 +Xve50NF9XkTrn6+cgqvfJ9ezE4cOaiD3Eov1u/HbHRx3K2Qf9IzvGoMCgYEAycgk +yOSYR0A3xKcfCQyOU0THEZWBBzd6zBAQ6VQNbcWHh1E8Ve0td6cvCxfydW1pIzXE +7b7J/BgMoC9oSQPfqQJQF2O0XESrdNgXjscfFpVgPfzbFQNgf7d0DSq4b/A5n5QR +HVMmWzVQoRQUwqTNeVxs0NpY6W6Meqv3i/KJqYECgYA/KyMyhM55fCqA5pmLgueV +Y/5/tMlTNcAxIgBLMnpeuaKUyI7ldveFVBClZmVQgpEo8/wpUw6+Kxvp4d32N+Ld +IGeeQSBQR3Gk3blCL3k/49tgKrUf7n7bsoIB8YVFdUjovRLzty2DcAoTjU2s2IgD +5mUgBGYPCV+6LEnjU6QjcwKBgGg+0FJBVzKoSKd+N5hzNixqwfWhqXFTBkvamQIS +fIWToTsVivhRekXwx2sRyh9EkSaxprW09aEZw5wWIehm6evk1//dcNaiW3oYEcOf +t73xGjGsKnsmrXoOCxSqV3LtRrfcxSLDTHOejbNKLpeIkOb8CvOzem/OvyC5K0DP +4rMBAoGBAJStRo5xQ2F9cyZW8vLd4eR3FHXxF/7Moxr6AyV3RLUjMhkwB8ZcFLUQ +dXI4NN9leDeIpNaGU6ozr+At3f50GtCWxdUppy9FDh5qDamBV8K4/+uNqFPiKFQ9 +uwNcJ8daMgVZ0QBrD3CBcSZQrfC484BlV6spJ3C16qDVSQPt7sAI +-----END RSA PRIVATE KEY-----""" +PRIVATE_KEYS = ( + pytest.param(private_key_2048_pkcs1, id="RSA_2048_PKCS1"), + pytest.param(private_key_4096_pkcs1, id="RSA_4096_PKCS1") +) + +LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM = b"""-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFADCCCSsCAQACggIBALUin0niNH7pESF9/AB5 +SgdWD6V/D1GDdiXb3zPl4zZi31NTO+DdFZncyF/ebJ3kBjvZAtsTCBPgCJbedmH/ +yoAWYIAJUvWNFcfA7Ei9h7zo4LgHV4q972C7wMsh4p/5lIrCTqnHBSgoRyo55NLl +8v9/Pta9e/3GE6e+m6yqVNSOJDNtDP/3W7ywVo388sPXobn6++GlcK/tMSX7AVa9 +qGkBcMP1xxs+vUO8hyug28WDuMOKtrCH3AuKU/F0zx6OCWdjO99xGvGux8bWUuet +/5oYUWS1OWsp0KcGlb9lPvgi+hLxrfE5TWTpHkb/MM/kbfAe9I86EaVSt+q0fqRy +pV4TBk+tfb/Ni53k3bKgNuVoti3f3NJ4rrpduAOvmmo9rvUlm8QPS5lbRZ7bzW0W +h1xNUi6Sz6CKfLqaRhdNjc9r95XfIAp001n6vwUPNEMvHtHKEUQARAma4yDMxxIO +jJaEQ8uJ2tKUUL+tVaIKkSg1Nq9/1XsxT0A293ImLGY1ga9x6TTpFI067y5hcjhP +UOUf6kBpnOgWLX5Oa5+4iH15ZCQGR14QcvhJQbogTPmEpBTO3R/drEiKGdOVeDD9 +PV3Kace5HcLCcu9krrLfR53fQe1d+WJ1Relu/dZVR53p4QiTs4kZpB+MSy2z5Gkk +9irNyBx+7VZbTOjbEZN4zXVTAgMBAAECggIBAK8ftCV4oAx7RWa+KWBD48DIAgSd +na/Pi/D6bQf+IPi6CvTCqkezOGkzvj6CCz1z8lr2av5nng2pMmS63HXPGndQKyhe +22gwaXhhG5EQPSX1eR4zav3muIMrwzAhqLvGT0kAp5EZq/CxUGyQ4JzOWWuQGK8B +L9mhIeuyK0x6ud1vN6zIqCLpgjYhvu00O3oBBomLEO+ORi6xAi2YSikU4Lf0/pNX +EpNSyyWsJnuV4CVMPtw/RnXSRHqb2KC/sGf4JztgA8j5z3UO6HNjT3BTF6ZiEH9v +fv4OxX5WrX0IZCL/ngumwedQ4XTItc8qdoTocyoOo5++IsVV/h7bNv3DIgD7tDWl +plPZ/IOx5l1nOPrkjv37LBoyNwy2CnecZ4a6uGt2TuaCS3jDvVO9fmJHLYNtz0x8 +DQBq7D2HYxnAZyRHbW12t9WoCOTwBuq4BlGN4kqFqhy4AU8a19OE1lby0IvMG8Ye +wRBdMmrDnmSsXRNiFmIO+cxrI5bsh5Tp6UWuZUn+imerYYfXnMxR2LUuA72AXGfY +WVv4PO4ntvEpARccZdG62LfbijWCb7mo1RvJE/NAAu1s4Nqu1pp6MMNyTSVGzbYb +OXaislEpTBTCAf7znjxRy6wOqg3wJ7EB4FG/Vh4VK2LB9tFyqakdjo5llNOW1eKU +jFvycBt+6PqcdcihAoIBAQDZ8MdHe5LBDLyY3jeaZQ8t3jC4nwSPuki/6OU1w6BT +OqycRftFD+9LqLmCatnSPgwkgOnPxLJ+1gDoq2Ne+2nSnAVfG4sALZih5xZwrP5M +AGavJTL0D7TTyf76tp+NJDuPIfH8W3hnJsgKhT2eGCksazwOHHWxyLhUrfVsmlHF +RZs3UxR1ZUT+9BF3Zlx7fOG8ljQeAh3qvM5qzcTzQIuuZxJFJLO4gUDLbr8ldvml +4wzkwTAneuiISIdomatTu5F1MIIdTh/YQmu6K4h8gHYJCnSglqHJmzzxDThE4I62 +gPcESF8DmEw29V+YTL4tZgh6PMYzI7uLMw9N8SVYu1q7AoIBAQDUxG5QpMRMoSam +jGRd1N3X9mK/2bR6rK06+JcAfzDipM5rkq0TsYXhJCiLMW4lfgMU77T0dRUK5am4 +UyUcMvqcOCjyez+H6tkf2Aar10jQQmpk7epdl3V6pwJNwPvnDhGdpVPbm34HXjdi +IfK4HZ9S1njkowlnxMZpKFOtf13usQDHlykEpRykp1/b6MbhNsSDR3lOmQCstZBp +qybRIlpyPG/AuLPH08g7VydS2rMrNIhztk89o1IX+CCcG/Oy5OyM3tjKyPqD81Q1 +5ZpG45AeLWMqIkU4/K5PKcAVvx1b9NNqYzi02uJM9ZEN5bGf9wLOM3zYu5lhIWnZ +hLACH2JJAoIBAQDTt/a/2Ko+ZFMq5mV51ccjNgB6ufBCeCOIW4Wf70Vm1U8uGUX6 +V3qOM4DT0117ws8k/x8kud71HIyRez3z3aV19h+5vxYPvDvUvJuuJkB8ML+QUkDn +nAJ85HSRtqvU/2fkqoNcNrgG7UPUBJBRbwNApYQX6UnkxitcCAqt0FSzoeUhn9H2 +IcUfMJdvOL+LL0xUWk6TAFdz3KtiUjeMYB3R9UtoZDk7ekUp25JRoPzxTFsQNyTC +lcIj8uGomfA4TbUG9XLRaT3CZvQkTXowCNOiAMg/4VWWdvqC6ebJ8qRxY2OUg4Ha +Ci+wDDsrxxHRJJgDt9qLf6EHnzi07Rjs1EVVAoIBAQC0seYmIuh7U9kpVM3gSmnl +gWA4IsH99SxhisFjMKHpuaF9BmJq+TcED9tG60HqIWyomTMK8WxfhtButF4t5rWj +eqZ72GQKIE8pliOESR+TjvQgp1WFCp5A/hkcw6qrfe1D/yaKuTF9PGy4sLAb4Txv +86lUM4pHUHxYzmDSVfsGPdi1qRCy2y7KP0NP1g8hMYwPGeJR9+r0wnXU5//dWNmL +bvxRpgs4yAmjK88/tHC5XrIL42bEqDGOHbJEIhEDexvSP2fKQIlRCpQX+djeH2FD +37P6EoTLcvzuSjzRuy9J61CpZ36/Sa0rQtpf/RSvD+6YBG4g+qG2NdRZYTDBfLnR +AoIBAQCCobzhbYqQ9Y6gzqYzqEfbCv5UUeW1VVkH2pjAzdQMJoyW1R0vIYbWFDP+ +LIdqddj+kYKDvHzg39bxHFhYd8cTWRNaTwj2iAg/YuVPjUbz89rwvdNB3K2i0a1B +Wkc8IajjpJ2CUgaxs1vgsd2EnmjgoJysiPAeYMOAtBtIi9XUrM9m0dTuBjTlX090 +eo6GRFwExaPynNi9GALwKQTVGL2NJG4yfyX0zudOtErFn7X+IsN464Up/UcE02Ha +v5BKxhVrwxiZ9jIroTHtqJzX1cyBkZnVMR8ItbpZLKQJ/35mO39IWabJA8HB8mZm +ymbpPjVPxSfCAHJr5Pcu5tuZ0knP +-----END PRIVATE KEY----- +""" + +PKCS8_PRIVATE_KEY = b"""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 +bbmdJJzw==""" +PKCS1_PRIVATE_KEY = b"""MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPp +X8PUYN2JdvfM+XjNmLfU1M74N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggA +lS9Y0Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r1 +7/cYTp76brKpU1I4kM20M//dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2oaQFww/XH +Gz69Q7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/mhhRZLU5a +ynQpwaVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKlXhMGT619v8 +2LneTdsqA25Wi2Ld/c0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaHXE1SLpLPoIp +8uppGF02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6MloRDy4na0pRQ +v61VogqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q5R/qQGmc6BYtf +k5rn7iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09Xcppx7kdwsJy72 +Sust9Hnd9B7V35YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2Ks3IHH7tVltM6Ns +Rk3jNdVMCAwEAAQKCAgEArx+0JXigDHtFZr4pYEPjwMgCBJ2dr8+L8PptB/4g+LoK +9MKqR7M4aTO+PoILPXPyWvZq/meeDakyZLrcdc8ad1ArKF7baDBpeGEbkRA9JfV5H +jNq/ea4gyvDMCGou8ZPSQCnkRmr8LFQbJDgnM5Za5AYrwEv2aEh67IrTHq53W83rM +ioIumCNiG+7TQ7egEGiYsQ745GLrECLZhKKRTgt/T+k1cSk1LLJawme5XgJUw+3D9 +GddJEepvYoL+wZ/gnO2ADyPnPdQ7oc2NPcFMXpmIQf29+/g7FflatfQhkIv+eC6bB +51DhdMi1zyp2hOhzKg6jn74ixVX+Hts2/cMiAPu0NaWmU9n8g7HmXWc4+uSO/fssG +jI3DLYKd5xnhrq4a3ZO5oJLeMO9U71+Ykctg23PTHwNAGrsPYdjGcBnJEdtbXa31a +gI5PAG6rgGUY3iSoWqHLgBTxrX04TWVvLQi8wbxh7BEF0yasOeZKxdE2IWYg75zGs +jluyHlOnpRa5lSf6KZ6thh9eczFHYtS4DvYBcZ9hZW/g87ie28SkBFxxl0brYt9uK +NYJvuajVG8kT80AC7Wzg2q7Wmnoww3JNJUbNths5dqKyUSlMFMIB/vOePFHLrA6qD +fAnsQHgUb9WHhUrYsH20XKpqR2OjmWU05bV4pSMW/JwG37o+px1yKECggEBANnwx0 +d7ksEMvJjeN5plDy3eMLifBI+6SL/o5TXDoFM6rJxF+0UP70uouYJq2dI+DCSA6c/ +Esn7WAOirY177adKcBV8biwAtmKHnFnCs/kwAZq8lMvQPtNPJ/vq2n40kO48h8fxb +eGcmyAqFPZ4YKSxrPA4cdbHIuFSt9WyaUcVFmzdTFHVlRP70EXdmXHt84byWNB4CH +eq8zmrNxPNAi65nEkUks7iBQMtuvyV2+aXjDOTBMCd66IhIh2iZq1O7kXUwgh1OH9 +hCa7oriHyAdgkKdKCWocmbPPENOETgjraA9wRIXwOYTDb1X5hMvi1mCHo8xjMju4s +zD03xJVi7WrsCggEBANTEblCkxEyhJqaMZF3U3df2Yr/ZtHqsrTr4lwB/MOKkzmuS +rROxheEkKIsxbiV+AxTvtPR1FQrlqbhTJRwy+pw4KPJ7P4fq2R/YBqvXSNBCamTt6 +l2XdXqnAk3A++cOEZ2lU9ubfgdeN2Ih8rgdn1LWeOSjCWfExmkoU61/Xe6xAMeXKQ +SlHKSnX9voxuE2xINHeU6ZAKy1kGmrJtEiWnI8b8C4s8fTyDtXJ1Lasys0iHO2Tz2 +jUhf4IJwb87Lk7Ize2MrI+oPzVDXlmkbjkB4tYyoiRTj8rk8pwBW/HVv002pjOLTa +4kz1kQ3lsZ/3As4zfNi7mWEhadmEsAIfYkkCggEBANO39r/Yqj5kUyrmZXnVxyM2A +Hq58EJ4I4hbhZ/vRWbVTy4ZRfpXeo4zgNPTXXvCzyT/HyS53vUcjJF7PfPdpXX2H7 +m/Fg+8O9S8m64mQHwwv5BSQOecAnzkdJG2q9T/Z+Sqg1w2uAbtQ9QEkFFvA0ClhBf +pSeTGK1wICq3QVLOh5SGf0fYhxR8wl284v4svTFRaTpMAV3Pcq2JSN4xgHdH1S2hk +OTt6RSnbklGg/PFMWxA3JMKVwiPy4aiZ8DhNtQb1ctFpPcJm9CRNejAI06IAyD/hV +ZZ2+oLp5snypHFjY5SDgdoKL7AMOyvHEdEkmAO32ot/oQefOLTtGOzURVUCggEBAL +Sx5iYi6HtT2SlUzeBKaeWBYDgiwf31LGGKwWMwoem5oX0GYmr5NwQP20brQeohbKi +ZMwrxbF+G0G60Xi3mtaN6pnvYZAogTymWI4RJH5OO9CCnVYUKnkD+GRzDqqt97UP/ +Joq5MX08bLiwsBvhPG/zqVQzikdQfFjOYNJV+wY92LWpELLbLso/Q0/WDyExjA8Z4 +lH36vTCddTn/91Y2Ytu/FGmCzjICaMrzz+0cLlesgvjZsSoMY4dskQiEQN7G9I/Z8 +pAiVEKlBf52N4fYUPfs/oShMty/O5KPNG7L0nrUKlnfr9JrStC2l/9FK8P7pgEbiD +6obY11FlhMMF8udECggEBAIKhvOFtipD1jqDOpjOoR9sK/lRR5bVVWQfamMDN1Awm +jJbVHS8hhtYUM/4sh2p12P6RgoO8fODf1vEcWFh3xxNZE1pPCPaICD9i5U+NRvPz2 +vC900HcraLRrUFaRzwhqOOknYJSBrGzW+Cx3YSeaOCgnKyI8B5gw4C0G0iL1dSsz2 +bR1O4GNOVfT3R6joZEXATFo/Kc2L0YAvApBNUYvY0kbjJ/JfTO5060SsWftf4iw3j +rhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGRmdUxHwi1ulkspAn/fmY7f0hZ +pskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8=""" + + +def _legacy_invalid_private_key_pkcs8_der(): + legacy_key = LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM.strip() + legacy_key = legacy_key[legacy_key.index(b"\n"):legacy_key.rindex(b"\n")] + return base64.b64decode(legacy_key) + + +def _actually_invalid_private_key_pkcs8_der(): + legacy_key = _legacy_invalid_private_key_pkcs8_der() + invalid_key = legacy_key[:len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER)] + invalid_key += b"\x00" + invalid_key += legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER):] + return invalid_key + + +def _actually_invalid_private_key_pkcs8_pem(): + invalid_key = b"-----BEGIN PRIVATE KEY-----\n" + invalid_key += base64.b64encode(_actually_invalid_private_key_pkcs8_der()) + invalid_key += b"\n-----END PRIVATE KEY-----\n" + return invalid_key + + +@pytest.mark.skipif(PurePythonRSAKey is None, reason="python-rsa backend not available") +class TestPurePythonRsa(object): + + def test_python_rsa_legacy_pem_read(self): + key = PurePythonRSAKey(LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM, ALGORITHMS.RS256) + new_pem = key.to_pem(pem_format="PKCS8") + assert new_pem != LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM + + def test_python_rsa_legacy_pem_invalid(self): + with pytest.raises(JWKError) as excinfo: + PurePythonRSAKey(_actually_invalid_private_key_pkcs8_pem(), ALGORITHMS.RS256) + + excinfo.match("Invalid private key encoding") + + def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1(self): + legacy_key = _legacy_invalid_private_key_pkcs8_der() + legacy_pkcs1 = legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER):] + + assert rsa_backend._legacy_private_key_pkcs8_to_pkcs1(legacy_key) == legacy_pkcs1 + + def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1_invalid(self): + invalid_key = _actually_invalid_private_key_pkcs8_der() + + with pytest.raises(ValueError) as excinfo: + rsa_backend._legacy_private_key_pkcs8_to_pkcs1(invalid_key) + + excinfo.match("Invalid private key encoding") + + def test_python_rsa_private_key_pkcs1_to_pkcs8(self): + pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) + pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) + + assert rsa_backend._private_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 + + def test_python_rsa_private_key_pkcs8_to_pkcs1(self): + pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) + pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) + + assert rsa_backend._private_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 @pytest.mark.pycrypto @@ -86,10 +340,12 @@ def test_pycrypto_RSA_key_instance(): long(65537))) PyCryptoRSAKey(key, ALGORITHMS.RS256) + # TODO: Unclear why this test was marked as only for pycrypto @pytest.mark.pycrypto @pytest.mark.pycryptodome -def test_pycrypto_unencoded_cleartext(): +@pytest.mark.parametrize("private_key", PRIVATE_KEYS) +def test_pycrypto_unencoded_cleartext(private_key): key = RSAKey(private_key, ALGORITHMS.RS256) msg = b'test' signature = key.sign(msg) @@ -120,7 +376,7 @@ def test_cryptography_RSA_key_instance(): class TestRSAAlgorithm: def test_RSA_key(self): - assert not RSAKey(private_key, ALGORITHMS.RS256).is_public() + assert not RSAKey(private_key_4096_pkcs1, ALGORITHMS.RS256).is_public() def test_string_secret(self): key = 'secret' @@ -139,7 +395,7 @@ def test_bad_cert(self,): def test_invalid_algorithm(self): with pytest.raises(JWKError): - RSAKey(private_key, ALGORITHMS.ES256) + RSAKey(private_key_4096_pkcs1, ALGORITHMS.ES256) with pytest.raises(JWKError): RSAKey({'kty': 'bla'}, ALGORITHMS.RS256) @@ -181,7 +437,8 @@ def test_RSA_jwk(self): # None of the extra parameters are present, but 'key' is still private. assert not RSAKey(key, ALGORITHMS.RS256).is_public() - def test_get_public_key(self): + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_get_public_key(self, private_key): key = RSAKey(private_key, ALGORITHMS.RS256) public_key = key.public_key() public_key2 = public_key.public_key() @@ -189,15 +446,16 @@ def test_get_public_key(self): assert public_key2.is_public() assert public_key == public_key2 - def test_to_pem(self): - key = RSAKey(private_key, ALGORITHMS.RS256) - assert key.to_pem(pem_format='PKCS1').strip() == private_key.strip() + @pytest.mark.parametrize("pkey", PRIVATE_KEYS) + def test_to_pem(self, pkey): + key = RSAKey(pkey, ALGORITHMS.RS256) + assert key.to_pem(pem_format='PKCS1').strip() == pkey.strip() pkcs8 = key.to_pem(pem_format='PKCS8').strip() - assert pkcs8 != private_key.strip() + assert pkcs8 != pkey.strip() newkey = RSAKey(pkcs8, ALGORITHMS.RS256) - assert newkey.to_pem(pem_format='PKCS1').strip() == private_key.strip() + assert newkey.to_pem(pem_format='PKCS1').strip() == pkey.strip() def assert_parameters(self, as_dict, private): assert isinstance(as_dict, dict) @@ -229,7 +487,8 @@ def assert_roundtrip(self, key): ALGORITHMS.RS256 ).to_dict() == key.to_dict() - def test_to_dict(self): + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_to_dict(self, private_key): key = RSAKey(private_key, ALGORITHMS.RS256) self.assert_parameters(key.to_dict(), private=True) self.assert_parameters(key.public_key().to_dict(), private=False) diff --git a/tests/algorithms/test_RSA_compat.py b/tests/algorithms/test_RSA_compat.py index c2359c2d..0da03d50 100644 --- a/tests/algorithms/test_RSA_compat.py +++ b/tests/algorithms/test_RSA_compat.py @@ -3,24 +3,32 @@ try: from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey from jose.backends.cryptography_backend import CryptographyRSAKey - from jose.backends.pycrypto_backend import RSAKey + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey except ImportError: - PurePythonRSAKey = CryptographyRSAKey = RSAKey = None + PurePythonRSAKey = CryptographyRSAKey = PyCryptoRSAKey = None from jose.constants import ALGORITHMS -from .test_RSA import private_key +from .test_RSA import PRIVATE_KEYS + +CRYPTO_BACKENDS = ( + pytest.param(PurePythonRSAKey, id="python_rsa"), + pytest.param(CryptographyRSAKey, id="pyca/cryptography"), + pytest.param(PyCryptoRSAKey, id="pycrypto/dome") +) +ENCODINGS = ("PKCS1", "PKCS8") @pytest.mark.backend_compatibility @pytest.mark.skipif( - None in (PurePythonRSAKey, CryptographyRSAKey, RSAKey), + None in (PurePythonRSAKey, CryptographyRSAKey, PyCryptoRSAKey), reason="Multiple crypto backends not available for backend compatibility tests" ) class TestBackendRsaCompatibility(object): - @pytest.mark.parametrize("BackendSign", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - @pytest.mark.parametrize("BackendVerify", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_signing_parity(self, BackendSign, BackendVerify): + @pytest.mark.parametrize("BackendSign", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendVerify", CRYPTO_BACKENDS) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_signing_parity(self, BackendSign, BackendVerify, private_key): key_sign = BackendSign(private_key, ALGORITHMS.RS256) key_verify = BackendVerify(private_key, ALGORITHMS.RS256).public_key() @@ -33,18 +41,62 @@ def test_signing_parity(self, BackendSign, BackendVerify): # invalid signature assert not key_verify.verify(msg, b'n' * 64) - @pytest.mark.parametrize("BackendFrom", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - @pytest.mark.parametrize("BackendTo", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_public_key_to_pem(self, BackendFrom, BackendTo): + @pytest.mark.parametrize("encoding", ENCODINGS) + @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_public_key_to_pem(self, BackendFrom, BackendTo, encoding, private_key): + key = BackendFrom(private_key, ALGORITHMS.RS256) + key2 = BackendTo(private_key, ALGORITHMS.RS256) + + key1_pem = key.public_key().to_pem(pem_format=encoding).strip() + key2_pem = key2.public_key().to_pem(pem_format=encoding).strip() + assert key1_pem == key2_pem + + @pytest.mark.parametrize("encoding", ENCODINGS) + @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_private_key_to_pem(self, BackendFrom, BackendTo, encoding, private_key): + key = BackendFrom(private_key, ALGORITHMS.RS256) + key2 = BackendTo(private_key, ALGORITHMS.RS256) + + key1_pem = key.to_pem(pem_format=encoding).strip() + key2_pem = key2.to_pem(pem_format=encoding).strip() + + import base64 + a = base64.b64decode(key1_pem[key1_pem.index(b"\n"):key1_pem.rindex(b"\n")]) + b = base64.b64decode(key2_pem[key2_pem.index(b"\n"):key2_pem.rindex(b"\n")]) + assert a == b + + assert key1_pem == key2_pem + + @pytest.mark.parametrize("encoding_save", ENCODINGS) + @pytest.mark.parametrize("encoding_load", ENCODINGS) + @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_public_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load, private_key): + key = BackendFrom(private_key, ALGORITHMS.RS256) + + pem_pub_reference = key.public_key().to_pem(pem_format=encoding_save).strip() + pem_pub_load = key.public_key().to_pem(pem_format=encoding_load).strip() + + pubkey_2 = BackendTo(pem_pub_load, ALGORITHMS.RS256) + + assert pem_pub_reference == pubkey_2.to_pem(encoding_save).strip() + + @pytest.mark.parametrize("encoding_save", ENCODINGS) + @pytest.mark.parametrize("encoding_load", ENCODINGS) + @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_private_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load, private_key): key = BackendFrom(private_key, ALGORITHMS.RS256) - pubkey = key.public_key() - pkcs1_pub = pubkey.to_pem(pem_format='PKCS1').strip() - pkcs8_pub = pubkey.to_pem(pem_format='PKCS8').strip() - assert pkcs1_pub != pkcs8_pub, BackendFrom + pem_reference = key.to_pem(pem_format=encoding_save).strip() + pem_load = key.to_pem(pem_format=encoding_load).strip() - pub1 = BackendTo(pkcs1_pub, ALGORITHMS.RS256) - pub8 = BackendTo(pkcs8_pub, ALGORITHMS.RS256) + key_2 = BackendTo(pem_load, ALGORITHMS.RS256) - assert pkcs8_pub == pub1.to_pem(pem_format='PKCS8').strip() - assert pkcs1_pub == pub8.to_pem(pem_format='PKCS1').strip() + assert pem_reference == key_2.to_pem(encoding_save).strip()