From fc2d1218e015adcf65ba01965884430c47895cd0 Mon Sep 17 00:00:00 2001
From: Joseph Tate <joseph@crunch.io>
Date: Tue, 14 Apr 2015 12:30:44 -0400
Subject: [PATCH] Support newer PyJWT (1.0.0). remove PyCrypto completely for
 cryptography and PyJWT helpers. Reformat some test certificates to be easier
 to maintain. Update documentation to match use of cryptography instead of
 PyCrypto

---
 CHANGELOG.rst                                      |  6 ++
 docs/faq.rst                                       | 13 ++--
 docs/feature_matrix.rst                            |  4 +-
 docs/oauth1/client.rst                             | 31 ++++-----
 oauthlib/common.py                                 | 16 +----
 oauthlib/oauth1/rfc5849/signature.py               | 41 +++++++-----
 .../oauth2/rfc6749/clients/service_application.py  |  3 -
 oauthlib/oauth2/rfc6749/utils.py                   |  2 +-
 requirements.txt                                   |  6 +-
 setup.py                                           |  8 +--
 tests/oauth1/rfc5849/test_signatures.py            | 44 ++++++-------
 .../rfc6749/clients/test_service_application.py    | 51 ++++++++-------
 tests/oauth2/rfc6749/test_server.py                | 73 +++++++++++++---------
 tests/oauth2/rfc6749/test_utils.py                 |  3 +
 14 files changed, 153 insertions(+), 148 deletions(-)

--- a/oauthlib/common.py
+++ b/oauthlib/common.py
@@ -229,11 +229,8 @@ def generate_token(length=30, chars=UNIC
 
 
 def generate_signed_token(private_pem, request):
-    import Crypto.PublicKey.RSA as RSA
     import jwt
 
-    private_key = RSA.importKey(private_pem)
-
     now = datetime.datetime.utcnow()
 
     claims = {
@@ -243,23 +240,16 @@ def generate_signed_token(private_pem, r
 
     claims.update(request.claims)
 
-    token = jwt.encode(claims, private_key, 'RS256')
+    token = jwt.encode(claims, private_pem, 'RS256')
     token = to_unicode(token, "UTF-8")
 
     return token
 
 
-def verify_signed_token(private_pem, token):
-    import Crypto.PublicKey.RSA as RSA
+def verify_signed_token(public_pem, token):
     import jwt
 
-    public_key = RSA.importKey(private_pem).publickey()
-
-    try:
-        # return jwt.verify_jwt(token.encode(), public_key)
-        return jwt.decode(token, public_key)
-    except:
-        raise Exception
+    return jwt.decode(token, public_pem, algorithms=['RS256'])
 
 
 def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET):
--- a/oauthlib/oauth1/rfc5849/signature.py
+++ b/oauthlib/oauth1/rfc5849/signature.py
@@ -464,6 +464,15 @@ def sign_hmac_sha1(base_string, client_s
     # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8
     return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
 
+_jwtrs1 = None
+
+#jwt has some nice pycrypto/cryptography abstractions
+def _jwt_rs1_signing_algorithm():
+    global _jwtrs1
+    if _jwtrs1 is None:
+        import jwt.algorithms as jwtalgo
+        _jwtrs1 = jwtalgo.RSAAlgorithm(jwtalgo.hashes.SHA1)
+    return _jwtrs1
 
 def sign_rsa_sha1(base_string, rsa_private_key):
     """**RSA-SHA1**
@@ -483,16 +492,13 @@ def sign_rsa_sha1(base_string, rsa_priva
     .. _`RFC3447, Section 8.2`: http://tools.ietf.org/html/rfc3447#section-8.2
 
     """
-    # TODO: finish RSA documentation
-    from Crypto.PublicKey import RSA
-    from Crypto.Signature import PKCS1_v1_5
-    from Crypto.Hash import SHA
-    key = RSA.importKey(rsa_private_key)
     if isinstance(base_string, unicode_type):
         base_string = base_string.encode('utf-8')
-    h = SHA.new(base_string)
-    p = PKCS1_v1_5.new(key)
-    return binascii.b2a_base64(p.sign(h))[:-1].decode('utf-8')
+    # TODO: finish RSA documentation
+    alg = _jwt_rs1_signing_algorithm()
+    key = _prepare_key_plus(alg, rsa_private_key)
+    s=alg.sign(base_string, key)
+    return binascii.b2a_base64(s)[:-1].decode('utf-8')
 
 
 def sign_rsa_sha1_with_client(base_string, client):
@@ -562,13 +568,17 @@ def verify_hmac_sha1(request, client_sec
                                resource_owner_secret)
     return safe_string_equals(signature, request.signature)
 
+def _prepare_key_plus(alg, keystr):
+    if isinstance(keystr, bytes_type):
+        keystr = keystr.decode('utf-8')
+    return alg.prepare_key(keystr)
 
 def verify_rsa_sha1(request, rsa_public_key):
     """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature.
 
     Per `section 3.4.3`_ of the spec.
 
-    Note this method requires the PyCrypto library.
+    Note this method requires the jwt and cryptography libraries.
 
     .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3
 
@@ -580,17 +590,14 @@ def verify_rsa_sha1(request, rsa_public_
 
     .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2
     """
-    from Crypto.PublicKey import RSA
-    from Crypto.Signature import PKCS1_v1_5
-    from Crypto.Hash import SHA
-    key = RSA.importKey(rsa_public_key)
     norm_params = normalize_parameters(request.params)
     uri = normalize_base_string_uri(request.uri)
-    message = construct_base_string(request.http_method, uri, norm_params)
-    h = SHA.new(message.encode('utf-8'))
-    p = PKCS1_v1_5.new(key)
+    message = construct_base_string(request.http_method, uri, norm_params).encode('utf-8')
     sig = binascii.a2b_base64(request.signature.encode('utf-8'))
-    return p.verify(h, sig)
+
+    alg = _jwt_rs1_signing_algorithm()
+    key = _prepare_key_plus(alg, rsa_public_key)
+    return alg.verify(message, key, sig)
 
 
 def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
--- a/oauthlib/oauth2/rfc6749/clients/service_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/service_application.py
@@ -140,14 +140,11 @@ class ServiceApplicationClient(Client):
         .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
         """
         import jwt
-        import Crypto.PublicKey.RSA as RSA
 
         key = private_key or self.private_key
         if not key:
             raise ValueError('An encryption key must be supplied to make JWT'
                              ' token requests.')
-        key = RSA.importKey(key)
-
         claim = {
             'iss': issuer or self.issuer,
             'aud': audience or self.issuer,
--- a/oauthlib/oauth2/rfc6749/utils.py
+++ b/oauthlib/oauth2/rfc6749/utils.py
@@ -24,7 +24,7 @@ def list_to_scope(scope):
     """Convert a list of scopes to a space separated string."""
     if isinstance(scope, unicode_type) or scope is None:
         return scope
-    elif isinstance(scope, list):
+    elif isinstance(scope, (tuple, list)):
         return " ".join([unicode_type(s) for s in scope])
     elif isinstance(scope, set):
         return list_to_scope(list(scope))
--- a/setup.py
+++ b/setup.py
@@ -18,11 +18,11 @@ def fread(fn):
         return f.read()
 
 if sys.version_info[0] == 3:
-    tests_require = ['nose', 'pycrypto', 'pyjwt', 'blinker']
+    tests_require = ['nose', 'cryptography', 'pyjwt>=1.0.0', 'blinker']
 else:
-    tests_require = ['nose', 'unittest2', 'pycrypto', 'mock', 'pyjwt', 'blinker']
-rsa_require = ['pycrypto']
-signedtoken_require = ['pycrypto', 'pyjwt']
+    tests_require = ['nose', 'unittest2', 'cryptography', 'mock', 'pyjwt>=1.0.0', 'blinker']
+rsa_require = ['cryptography']
+signedtoken_require = ['cryptography', 'pyjwt>=1.0.0']
 signals_require = ['blinker']
 
 requires = []
--- a/tests/oauth1/rfc5849/test_signatures.py
+++ b/tests/oauth1/rfc5849/test_signatures.py
@@ -269,31 +269,25 @@ class SignatureTests(TestCase):
         b"th_nonce%253D%25227d8f3e4a%2522%252Coauth_signature"
         b"%253D%2522bYT5CMsGcbgUdFHObYMEfcx6bsw%25253D%2522")
 
-    @property
-    def rsa_private_key(self):
-        # Generated using: $ openssl genrsa -out <key>.pem 1024
-        # PyCrypto / python-rsa requires the key to be concatenated with
-        # linebreaks.
-        return (
-            b"-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDk1/bxy"
-            b"S8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG\nAlwXWfzXw"
-            b"SMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVa"
-            b"h\n5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8mfvGGg3xNjT"
-            b"MO7IdrwIDAQAB\nAoGBAOQ2KuH8S5+OrsL4K+wfjoCi6MfxCUyqVU9"
-            b"GxocdM1m30WyWRFMEz2nKJ8fR\np3vTD4w8yplTOhcoXdQZl0kRoaD"
-            b"zrcYkm2VvJtQRrX7dKFT8dR8D/Tr7dNQLOXfC\nDY6xveQczE7qt7V"
-            b"k7lp4FqmxBsaaEuokt78pOOjywZoInjZhAkEA9wz3zoZNT0/i\nrf6"
-            b"qv2qTIeieUB035N3dyw6f1BGSWYaXSuerDCD/J1qZbAPKKhyHZbVaw"
-            b"Ft3UMhe\n542UftBaxQJBAO0iJy1I8GQjGnS7B3yvyH3CcLYGy296+"
-            b"XO/2xKp/d/ty1OIeovx\nC60pLNwuFNF3z9d2GVQAdoQ89hUkOtjZL"
-            b"eMCQQD0JO6oPHUeUjYT+T7ImAv7UKVT\nSuy30sKjLzqoGw1kR+wv7"
-            b"C5PeDRvscs4wa4CW9s6mjSrMDkDrmCLuJDtmf55AkEA\nkmaMg2PNr"
-            b"jUR51F0zOEFycaaqXbGcFwe1/xx9zLmHzMDXd4bsnwt9kk+fe0hQzV"
-            b"S\nJzatanQit3+feev1PN3QewJAWv4RZeavEUhKv+kLe95Yd0su7lT"
-            b"LVduVgh4v5yLT\nGa6FHdjGPcfajt+nrpB1n8UQBEH9ZxniokR/IPv"
-            b"dMlxqXA==\n-----END RSA PRIVATE KEY-----"
-        )
-
+    # Generated using: $ openssl genrsa -out <key>.pem 1024
+    # PEM encoding requires the key to be concatenated with
+    # linebreaks.
+    rsa_private_key = b"""-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDk1/bxyS8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG
+AlwXWfzXwSMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVah
+5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8mfvGGg3xNjTMO7IdrwIDAQAB
+AoGBAOQ2KuH8S5+OrsL4K+wfjoCi6MfxCUyqVU9GxocdM1m30WyWRFMEz2nKJ8fR
+p3vTD4w8yplTOhcoXdQZl0kRoaDzrcYkm2VvJtQRrX7dKFT8dR8D/Tr7dNQLOXfC
+DY6xveQczE7qt7Vk7lp4FqmxBsaaEuokt78pOOjywZoInjZhAkEA9wz3zoZNT0/i
+rf6qv2qTIeieUB035N3dyw6f1BGSWYaXSuerDCD/J1qZbAPKKhyHZbVawFt3UMhe
+542UftBaxQJBAO0iJy1I8GQjGnS7B3yvyH3CcLYGy296+XO/2xKp/d/ty1OIeovx
+C60pLNwuFNF3z9d2GVQAdoQ89hUkOtjZLeMCQQD0JO6oPHUeUjYT+T7ImAv7UKVT
+Suy30sKjLzqoGw1kR+wv7C5PeDRvscs4wa4CW9s6mjSrMDkDrmCLuJDtmf55AkEA
+kmaMg2PNrjUR51F0zOEFycaaqXbGcFwe1/xx9zLmHzMDXd4bsnwt9kk+fe0hQzVS
+JzatanQit3+feev1PN3QewJAWv4RZeavEUhKv+kLe95Yd0su7lTLVduVgh4v5yLT
+Ga6FHdjGPcfajt+nrpB1n8UQBEH9ZxniokR/IPvdMlxqXA==
+-----END RSA PRIVATE KEY-----
+"""
     @property
     def control_signature_rsa_sha1(self):
         # Base string saved in "<message>". Signature obtained using:
--- a/tests/oauth2/rfc6749/clients/test_service_application.py
+++ b/tests/oauth2/rfc6749/clients/test_service_application.py
@@ -5,7 +5,6 @@ import os
 from time import time
 
 import jwt
-from Crypto.PublicKey import RSA
 from mock import patch
 
 from oauthlib.common import Request
@@ -18,25 +17,32 @@ class ServiceApplicationClientTest(TestC
 
     gt = ServiceApplicationClient.grant_type
 
-    private_key = (
-        "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDk1/bxy"
-        "S8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG\nAlwXWfzXw"
-        "SMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVa"
-        "h\n5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8mfvGGg3xNjT"
-        "MO7IdrwIDAQAB\nAoGBAOQ2KuH8S5+OrsL4K+wfjoCi6MfxCUyqVU9"
-        "GxocdM1m30WyWRFMEz2nKJ8fR\np3vTD4w8yplTOhcoXdQZl0kRoaD"
-        "zrcYkm2VvJtQRrX7dKFT8dR8D/Tr7dNQLOXfC\nDY6xveQczE7qt7V"
-        "k7lp4FqmxBsaaEuokt78pOOjywZoInjZhAkEA9wz3zoZNT0/i\nrf6"
-        "qv2qTIeieUB035N3dyw6f1BGSWYaXSuerDCD/J1qZbAPKKhyHZbVaw"
-        "Ft3UMhe\n542UftBaxQJBAO0iJy1I8GQjGnS7B3yvyH3CcLYGy296+"
-        "XO/2xKp/d/ty1OIeovx\nC60pLNwuFNF3z9d2GVQAdoQ89hUkOtjZL"
-        "eMCQQD0JO6oPHUeUjYT+T7ImAv7UKVT\nSuy30sKjLzqoGw1kR+wv7"
-        "C5PeDRvscs4wa4CW9s6mjSrMDkDrmCLuJDtmf55AkEA\nkmaMg2PNr"
-        "jUR51F0zOEFycaaqXbGcFwe1/xx9zLmHzMDXd4bsnwt9kk+fe0hQzV"
-        "S\nJzatanQit3+feev1PN3QewJAWv4RZeavEUhKv+kLe95Yd0su7lT"
-        "LVduVgh4v5yLT\nGa6FHdjGPcfajt+nrpB1n8UQBEH9ZxniokR/IPv"
-        "dMlxqXA==\n-----END RSA PRIVATE KEY-----"
-    )
+    private_key = """
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDk1/bxyS8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG
+AlwXWfzXwSMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVah
+5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8mfvGGg3xNjTMO7IdrwIDAQAB
+AoGBAOQ2KuH8S5+OrsL4K+wfjoCi6MfxCUyqVU9GxocdM1m30WyWRFMEz2nKJ8fR
+p3vTD4w8yplTOhcoXdQZl0kRoaDzrcYkm2VvJtQRrX7dKFT8dR8D/Tr7dNQLOXfC
+DY6xveQczE7qt7Vk7lp4FqmxBsaaEuokt78pOOjywZoInjZhAkEA9wz3zoZNT0/i
+rf6qv2qTIeieUB035N3dyw6f1BGSWYaXSuerDCD/J1qZbAPKKhyHZbVawFt3UMhe
+542UftBaxQJBAO0iJy1I8GQjGnS7B3yvyH3CcLYGy296+XO/2xKp/d/ty1OIeovx
+C60pLNwuFNF3z9d2GVQAdoQ89hUkOtjZLeMCQQD0JO6oPHUeUjYT+T7ImAv7UKVT
+Suy30sKjLzqoGw1kR+wv7C5PeDRvscs4wa4CW9s6mjSrMDkDrmCLuJDtmf55AkEA
+kmaMg2PNrjUR51F0zOEFycaaqXbGcFwe1/xx9zLmHzMDXd4bsnwt9kk+fe0hQzVS
+JzatanQit3+feev1PN3QewJAWv4RZeavEUhKv+kLe95Yd0su7lTLVduVgh4v5yLT
+Ga6FHdjGPcfajt+nrpB1n8UQBEH9ZxniokR/IPvdMlxqXA==
+-----END RSA PRIVATE KEY-----
+"""
+
+    public_key = """
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDk1/bxyS8Q8jiheHeYYp/4rEKJ
+opeQRRKKpZI4s5i+UPwVpupGAlwXWfzXwSMaKPAoKJNdu7tqKRniqst5uoHXw98g
+j0x7zamu0Ck1LtQ4c7pFMVah5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8
+mfvGGg3xNjTMO7IdrwIDAQAB
+-----END PUBLIC KEY-----
+"""
 
     subject = 'resource-owner@provider.com'
 
@@ -86,11 +92,10 @@ class ServiceApplicationClientTest(TestC
         self.assertEqual(r.isnot, 'empty') 
         self.assertEqual(r.grant_type, ServiceApplicationClient.grant_type) 
 
-        key = RSA.importKey(self.private_key).publickey()
-        claim = jwt.decode(r.assertion, key)
+        claim = jwt.decode(r.assertion, self.public_key, audience=self.audience, algorithms=['RS256'])
 
         self.assertEqual(claim['iss'], self.issuer)
-        self.assertEqual(claim['aud'], self.audience)
+        # audience verification is handled during decode now
         self.assertEqual(claim['sub'], self.subject)
         self.assertEqual(claim['iat'], int(t.return_value))
 
--- a/tests/oauth2/rfc6749/test_server.py
+++ b/tests/oauth2/rfc6749/test_server.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import, unicode_literals
 from ...unittest import TestCase
-import Crypto.PublicKey.RSA as RSA
 import json
 import jwt
 import mock
@@ -177,35 +176,47 @@ class SignedTokenEndpointTest(TestCase):
         self.mock_validator.authenticate_client.side_effect = set_user
         self.addCleanup(setattr, self, 'mock_validator', mock.MagicMock())
 
-        self.private_pem = (
-            "-----BEGIN RSA PRIVATE KEY-----\n"
-            "MIIEpAIBAAKCAQEA6TtDhWGwzEOWZP6m/zHoZnAPLABfetvoMPmxPGjFjtDuMRPv\n"
-            "EvI1sbixZBjBtdnc5rTtHUUQ25Am3JzwPRGo5laMGbj1pPyCPxlVi9LK82HQNX0B\n"
-            "YK7tZtVfDHElQA7F4v3j9d3rad4O9/n+lyGIQ0tT7yQcBm2A8FEaP0bZYCLMjwMN\n"
-            "WfaVLE8eXHyv+MfpNNLI9wttLxygKYM48I3NwsFuJgOa/KuodXaAmf8pJnx8t1Wn\n"
-            "nxvaYXFiUn/TxmhM/qhemPa6+0nqq+aWV5eT7xn4K/ghLgNs09v6Yge0pmPl9Oz+\n"
-            "+bjJ+aKRnAmwCOY8/5U5EilAiUOeBoO9+8OXtwIDAQABAoIBAGFTTbXXMkPK4HN8\n"
-            "oItVdDlrAanG7hECuz3UtFUVE3upS/xG6TjqweVLwRqYCh2ssDXFwjy4mXRGDzF4\n"
-            "e/e/6s9Txlrlh/w1MtTJ6ZzTdcViR9RKOczysjZ7S5KRlI3KnGFAuWPcG2SuOWjZ\n"
-            "dZfzcj1Crd/ZHajBAVFHRsCo/ATVNKbTRprFfb27xKpQ2BwH/GG781sLE3ZVNIhs\n"
-            "aRRaED4622kI1E/WXws2qQMqbFKzo0m1tPbLb3Z89WgZJ/tRQwuDype1Vfm7k6oX\n"
-            "xfbp3948qSe/yWKRlMoPkleji/WxPkSIalzWSAi9ziN/0Uzhe65FURgrfHL3XR1A\n"
-            "B8UR+aECgYEA7NPQZV4cAikk02Hv65JgISofqV49P8MbLXk8sdnI1n7Mj10TgzU3\n"
-            "lyQGDEX4hqvT0bTXe4KAOxQZx9wumu05ejfzhdtSsEm6ptGHyCdmYDQeV0C/pxDX\n"
-            "JNCK8XgMku2370XG0AnyBCT7NGlgtDcNCQufcesF2gEuoKiXg6Zjo7sCgYEA/Bzs\n"
-            "9fWGZZnSsMSBSW2OYbFuhF3Fne0HcxXQHipl0Rujc/9g0nccwqKGizn4fGOE7a8F\n"
-            "usQgJoeGcinL7E9OEP/uQ9VX1C9RNVjIxP1O5/Guw1zjxQQYetOvbPhN2QhD1Ye7\n"
-            "0TRKrW1BapcjwLpFQlVg1ZeTPOi5lv24W/wX9jUCgYEAkrMSX/hPuTbrTNVZ3L6r\n"
-            "NV/2hN+PaTPeXei/pBuXwOaCqDurnpcUfFcgN/IP5LwDVd+Dq0pHTFFDNv45EFbq\n"
-            "R77o5n3ZVsIVEMiyJ1XgoK8oLDw7e61+15smtjT69Piz+09pu+ytMcwGn4y3Dmsb\n"
-            "dALzHYnL8iLRU0ubrz0ec4kCgYAJiVKRTzNBPptQom49h85d9ac3jJCAE8o3WTjh\n"
-            "Gzt0uHXrWlqgO280EY/DTnMOyXjqwLcXxHlu26uDP/99tdY/IF8z46sJ1KxetzgI\n"
-            "84f7kBHLRAU9m5UNeFpnZdEUB5MBTbwWAsNcYgiabpMkpCcghjg+fBhOsoLqqjhC\n"
-            "CnwhjQKBgQDkv0QTdyBU84TE8J0XY3eLQwXbrvG2yD5A2ntN3PyxGEneX5WTJGMZ\n"
-            "xJxwaFYQiDS3b9E7b8Q5dg8qa5Y1+epdhx3cuQAWPm+AoHKshDfbRve4txBDQAqh\n"
-            "c6MxSWgsa+2Ld5SWSNbGtpPcmEM3Fl5ttMCNCKtNc0UE16oHwaPAIw==\n"
-            "-----END RSA PRIVATE KEY-----"
-        )
+        self.private_pem = """
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6TtDhWGwzEOWZP6m/zHoZnAPLABfetvoMPmxPGjFjtDuMRPv
+EvI1sbixZBjBtdnc5rTtHUUQ25Am3JzwPRGo5laMGbj1pPyCPxlVi9LK82HQNX0B
+YK7tZtVfDHElQA7F4v3j9d3rad4O9/n+lyGIQ0tT7yQcBm2A8FEaP0bZYCLMjwMN
+WfaVLE8eXHyv+MfpNNLI9wttLxygKYM48I3NwsFuJgOa/KuodXaAmf8pJnx8t1Wn
+nxvaYXFiUn/TxmhM/qhemPa6+0nqq+aWV5eT7xn4K/ghLgNs09v6Yge0pmPl9Oz+
++bjJ+aKRnAmwCOY8/5U5EilAiUOeBoO9+8OXtwIDAQABAoIBAGFTTbXXMkPK4HN8
+oItVdDlrAanG7hECuz3UtFUVE3upS/xG6TjqweVLwRqYCh2ssDXFwjy4mXRGDzF4
+e/e/6s9Txlrlh/w1MtTJ6ZzTdcViR9RKOczysjZ7S5KRlI3KnGFAuWPcG2SuOWjZ
+dZfzcj1Crd/ZHajBAVFHRsCo/ATVNKbTRprFfb27xKpQ2BwH/GG781sLE3ZVNIhs
+aRRaED4622kI1E/WXws2qQMqbFKzo0m1tPbLb3Z89WgZJ/tRQwuDype1Vfm7k6oX
+xfbp3948qSe/yWKRlMoPkleji/WxPkSIalzWSAi9ziN/0Uzhe65FURgrfHL3XR1A
+B8UR+aECgYEA7NPQZV4cAikk02Hv65JgISofqV49P8MbLXk8sdnI1n7Mj10TgzU3
+lyQGDEX4hqvT0bTXe4KAOxQZx9wumu05ejfzhdtSsEm6ptGHyCdmYDQeV0C/pxDX
+JNCK8XgMku2370XG0AnyBCT7NGlgtDcNCQufcesF2gEuoKiXg6Zjo7sCgYEA/Bzs
+9fWGZZnSsMSBSW2OYbFuhF3Fne0HcxXQHipl0Rujc/9g0nccwqKGizn4fGOE7a8F
+usQgJoeGcinL7E9OEP/uQ9VX1C9RNVjIxP1O5/Guw1zjxQQYetOvbPhN2QhD1Ye7
+0TRKrW1BapcjwLpFQlVg1ZeTPOi5lv24W/wX9jUCgYEAkrMSX/hPuTbrTNVZ3L6r
+NV/2hN+PaTPeXei/pBuXwOaCqDurnpcUfFcgN/IP5LwDVd+Dq0pHTFFDNv45EFbq
+R77o5n3ZVsIVEMiyJ1XgoK8oLDw7e61+15smtjT69Piz+09pu+ytMcwGn4y3Dmsb
+dALzHYnL8iLRU0ubrz0ec4kCgYAJiVKRTzNBPptQom49h85d9ac3jJCAE8o3WTjh
+Gzt0uHXrWlqgO280EY/DTnMOyXjqwLcXxHlu26uDP/99tdY/IF8z46sJ1KxetzgI
+84f7kBHLRAU9m5UNeFpnZdEUB5MBTbwWAsNcYgiabpMkpCcghjg+fBhOsoLqqjhC
+CnwhjQKBgQDkv0QTdyBU84TE8J0XY3eLQwXbrvG2yD5A2ntN3PyxGEneX5WTJGMZ
+xJxwaFYQiDS3b9E7b8Q5dg8qa5Y1+epdhx3cuQAWPm+AoHKshDfbRve4txBDQAqh
+c6MxSWgsa+2Ld5SWSNbGtpPcmEM3Fl5ttMCNCKtNc0UE16oHwaPAIw==
+-----END RSA PRIVATE KEY-----
+        """
+
+        self.public_pem = """
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6TtDhWGwzEOWZP6m/zHo
+ZnAPLABfetvoMPmxPGjFjtDuMRPvEvI1sbixZBjBtdnc5rTtHUUQ25Am3JzwPRGo
+5laMGbj1pPyCPxlVi9LK82HQNX0BYK7tZtVfDHElQA7F4v3j9d3rad4O9/n+lyGI
+Q0tT7yQcBm2A8FEaP0bZYCLMjwMNWfaVLE8eXHyv+MfpNNLI9wttLxygKYM48I3N
+wsFuJgOa/KuodXaAmf8pJnx8t1WnnxvaYXFiUn/TxmhM/qhemPa6+0nqq+aWV5eT
+7xn4K/ghLgNs09v6Yge0pmPl9Oz++bjJ+aKRnAmwCOY8/5U5EilAiUOeBoO9+8OX
+twIDAQAB
+-----END PUBLIC KEY-----
+        """
 
         signed_token = tokens.signed_token_generator(self.private_pem,
                                                      user_id=123)
@@ -254,7 +265,7 @@ class SignedTokenEndpointTest(TestCase):
 
         access_token = json.loads(body)['access_token']
 
-        claims = common.verify_signed_token(self.private_pem, access_token)
+        claims = common.verify_signed_token(self.public_pem, access_token)
 
         self.assertEqual(claims['scope'], 'all of them')
         self.assertEqual(claims['user_id'], 123)
--- a/tests/oauth2/rfc6749/test_utils.py
+++ b/tests/oauth2/rfc6749/test_utils.py
@@ -73,6 +73,9 @@ class UtilsTests(TestCase):
         string_list = ['foo', 'bar', 'baz']
         self.assertEqual(list_to_scope(string_list), expected)
 
+        string_tuple = ('foo', 'bar', 'baz')
+        self.assertEqual(list_to_scope(string_tuple), expected)
+
         obj_list = [ScopeObject('foo'), ScopeObject('bar'), ScopeObject('baz')]
         self.assertEqual(list_to_scope(obj_list), expected)
 
