def test_encode_decode_with_rsa_sha512(self): try: from Crypto.PublicKey import RSA # RSA-formatted key with open('tests/testkey', 'r') as rsa_priv_file: priv_rsakey = RSA.importKey(rsa_priv_file.read()) jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS512') with open('tests/testkey.pub', 'r') as rsa_pub_file: pub_rsakey = RSA.importKey(rsa_pub_file.read()) assert jwt.decode(jwt_message, pub_rsakey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_rsakey, *load_output) # string-formatted key with open('tests/testkey', 'r') as rsa_priv_file: priv_rsakey = rsa_priv_file.read() jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS512') with open('tests/testkey.pub', 'r') as rsa_pub_file: pub_rsakey = rsa_pub_file.read() assert jwt.decode(jwt_message, pub_rsakey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_rsakey, *load_output) except ImportError: pass
def test_encode_decode_with_ecdsa_sha512(self): # PEM-formatted EC key with open('tests/testkey_ec', 'r') as ec_priv_file: priv_eckey = load_pem_private_key(ensure_bytes(ec_priv_file.read()), password=None, backend=default_backend()) jwt_message = jwt.encode(self.payload, priv_eckey, algorithm='ES512') with open('tests/testkey_ec.pub', 'r') as ec_pub_file: pub_eckey = load_pem_public_key(ensure_bytes(ec_pub_file.read()), backend=default_backend()) assert jwt.decode(jwt_message, pub_eckey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_eckey, *load_output) # string-formatted key with open('tests/testkey_ec', 'r') as ec_priv_file: priv_eckey = ec_priv_file.read() jwt_message = jwt.encode(self.payload, priv_eckey, algorithm='ES512') with open('tests/testkey_ec.pub', 'r') as ec_pub_file: pub_eckey = ec_pub_file.read() assert jwt.decode(jwt_message, pub_eckey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_eckey, *load_output)
def test_encode_decode_with_rsa_sha384(self): # PEM-formatted RSA key with open('tests/testkey_rsa', 'r') as rsa_priv_file: priv_rsakey = load_pem_private_key(ensure_bytes(rsa_priv_file.read()), password=None, backend=default_backend()) jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS384') with open('tests/testkey_rsa.pub', 'r') as rsa_pub_file: pub_rsakey = load_ssh_public_key(ensure_bytes(rsa_pub_file.read()), backend=default_backend()) assert jwt.decode(jwt_message, pub_rsakey) # string-formatted key with open('tests/testkey_rsa', 'r') as rsa_priv_file: priv_rsakey = rsa_priv_file.read() jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS384') with open('tests/testkey_rsa.pub', 'r') as rsa_pub_file: pub_rsakey = rsa_pub_file.read() assert jwt.decode(jwt_message, pub_rsakey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_rsakey, *load_output)
def test_encode_decode_with_ecdsa_sha512(self): try: import ecdsa # PEM-formatted EC key with open('tests/testkey_ec', 'r') as ec_priv_file: priv_eckey = ecdsa.SigningKey.from_pem(ec_priv_file.read()) jwt_message = jwt.encode(self.payload, priv_eckey, algorithm='ES512') with open('tests/testkey_ec.pub', 'r') as ec_pub_file: pub_eckey = ecdsa.VerifyingKey.from_pem(ec_pub_file.read()) assert jwt.decode(jwt_message, pub_eckey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_eckey, *load_output) # string-formatted key with open('tests/testkey_ec', 'r') as ec_priv_file: priv_eckey = ec_priv_file.read() jwt_message = jwt.encode(self.payload, priv_eckey, algorithm='ES512') with open('tests/testkey_ec.pub', 'r') as ec_pub_file: pub_eckey = ec_pub_file.read() assert jwt.decode(jwt_message, pub_eckey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_eckey, *load_output) except ImportError: pass
def test_encode_decode_with_rsa_sha512(self): try: from Crypto.PublicKey import RSA # PEM-formatted RSA key with open('tests/testkey_rsa', 'r') as rsa_priv_file: priv_rsakey = RSA.importKey(rsa_priv_file.read()) jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS512') with open('tests/testkey_rsa.pub', 'r') as rsa_pub_file: pub_rsakey = RSA.importKey(rsa_pub_file.read()) assert jwt.decode(jwt_message, pub_rsakey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_rsakey, *load_output) # string-formatted key with open('tests/testkey_rsa', 'r') as rsa_priv_file: priv_rsakey = rsa_priv_file.read() jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS512') with open('tests/testkey_rsa.pub', 'r') as rsa_pub_file: pub_rsakey = rsa_pub_file.read() assert jwt.decode(jwt_message, pub_rsakey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_rsakey, *load_output) except ImportError: pass
def test_decode_skip_expiration_verification(self): self.payload['exp'] = time.time() - 1 secret = 'secret' jwt_message = jwt.encode(self.payload, secret) jwt.decode(jwt_message, secret, verify_expiration=False) decoded_payload, signing, header, signature = jwt.load(jwt_message) jwt.verify_signature(decoded_payload, signing, header, signature, secret, verify_expiration=False)
def test_decode_skip_notbefore_verification(self): self.payload['nbf'] = time.time() + 10 secret = 'secret' jwt_message = jwt.encode(self.payload, secret) jwt.decode(jwt_message, secret, verify_expiration=False) decoded_payload, signing, header, signature = jwt.load(jwt_message) jwt.verify_signature(decoded_payload, signing, header, signature, secret, verify_expiration=False)
def test_load_verify_valid_jwt(self): example_payload = {"hello": "world"} example_secret = "secret" example_jwt = (b"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" b".eyJoZWxsbyI6ICJ3b3JsZCJ9" b".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8") decoded_payload, signing, header, signature = jwt.load(example_jwt) jwt.verify_signature(decoded_payload, signing, header, signature, example_secret) self.assertEqual(decoded_payload, example_payload)
def test_load_verify_valid_jwt(self): example_payload = {'hello': 'world'} example_secret = 'secret' example_jwt = ( b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9' b'.eyJoZWxsbyI6ICJ3b3JsZCJ9' b'.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8') decoded_payload, signing, header, signature = jwt.load(example_jwt) jwt.verify_signature(decoded_payload, signing, header, signature, example_secret) self.assertEqual(decoded_payload, example_payload)
def test_bytes_secret(self): secret = b'\xc2' # char value that ascii codec cannot decode jwt_message = jwt.encode(self.payload, secret) decoded_payload = jwt.decode(jwt_message, secret) self.assertEqual(decoded_payload, self.payload) decoded_payload, signing, header, signature = jwt.load(jwt_message) jwt.verify_signature(decoded_payload, signing, header, signature, secret) self.assertEqual(decoded_payload, self.payload)
def test_unicode_secret(self): secret = '\xc2' jwt_message = jwt.encode(self.payload, secret) decoded_payload = jwt.decode(jwt_message, secret) self.assertEqual(decoded_payload, self.payload) decoded_payload, signing, header, signature = jwt.load(jwt_message) jwt.verify_signature(decoded_payload, signing, header, signature, secret) self.assertEqual(decoded_payload, self.payload)
def remote_user_jwt(request): # type: (HttpRequest) -> HttpResponse try: json_web_token = request.POST["json_web_token"] payload, signing_input, header, signature = jwt.load(json_web_token) except KeyError: raise JsonableError(_("No JSON web token passed in request")) except jwt.DecodeError: raise JsonableError(_("Bad JSON web token")) remote_user = payload.get("user", None) if remote_user is None: raise JsonableError(_("No user specified in JSON web token claims")) domain = payload.get('realm', None) if domain is None: raise JsonableError(_("No domain specified in JSON web token claims")) email = "%s@%s" % (remote_user, domain) try: jwt.verify_signature(payload, signing_input, header, signature, settings.JWT_AUTH_KEYS[domain]) # We do all the authentication we need here (otherwise we'd have to # duplicate work), but we need to call authenticate with some backend so # that the request.backend attribute gets set. return_data = {} # type: Dict[str, bool] user_profile = authenticate(username=email, realm_subdomain=get_subdomain(request), return_data=return_data, use_dummy_backend=True) if return_data.get('invalid_subdomain'): logging.warning( "User attempted to JWT login to wrong subdomain %s: %s" % ( get_subdomain(request), email, )) raise JsonableError(_("Wrong subdomain")) except (jwt.DecodeError, jwt.ExpiredSignature): raise JsonableError(_("Bad JSON web token signature")) except KeyError: raise JsonableError(_("Realm not authorized for JWT login")) except UserProfile.DoesNotExist: user_profile = None return login_or_register_remote_user(request, email, user_profile, remote_user)
def test_verify_signature_no_secret(self): right_secret = 'foo' jwt_message = jwt.encode(self.payload, right_secret) decoded_payload, signing, header, signature = jwt.load(jwt_message) self.assertRaises( jwt.DecodeError, lambda: jwt.verify_signature( decoded_payload, signing, header, signature))
def test_encode_decode_with_rsa_sha384(self): try: from Crypto.PublicKey import RSA with open('tests/testkey', 'r') as rsa_priv_file: priv_rsakey = RSA.importKey(rsa_priv_file.read()) jwt_message = jwt.encode(self.payload, priv_rsakey, algorithm='RS384') with open('tests/testkey.pub', 'r') as rsa_pub_file: pub_rsakey = RSA.importKey(rsa_pub_file.read()) assert jwt.decode(jwt_message, pub_rsakey) load_output = jwt.load(jwt_message) jwt.verify_signature(key=pub_rsakey, *load_output) except ImportError: pass
def test_verify_signature_no_secret(self): right_secret = 'foo' jwt_message = jwt.encode(self.payload, right_secret) decoded_payload, signing, header, signature = jwt.load(jwt_message) self.assertRaises( jwt.DecodeError, lambda: jwt.verify_signature(decoded_payload, signing, header, signature))
def remote_user_jwt(request): # type: (HttpRequest) -> HttpResponse try: json_web_token = request.POST["json_web_token"] payload, signing_input, header, signature = jwt.load(json_web_token) except KeyError: raise JsonableError(_("No JSON web token passed in request")) except jwt.DecodeError: raise JsonableError(_("Bad JSON web token")) remote_user = payload.get("user", None) if remote_user is None: raise JsonableError(_("No user specified in JSON web token claims")) domain = payload.get('realm', None) if domain is None: raise JsonableError(_("No domain specified in JSON web token claims")) email = "%s@%s" % (remote_user, domain) try: jwt.verify_signature(payload, signing_input, header, signature, settings.JWT_AUTH_KEYS[domain]) # We do all the authentication we need here (otherwise we'd have to # duplicate work), but we need to call authenticate with some backend so # that the request.backend attribute gets set. return_data = {} # type: Dict[str, bool] user_profile = authenticate(username=email, realm_subdomain=get_subdomain(request), return_data=return_data, use_dummy_backend=True) if return_data.get('invalid_subdomain'): logging.warning("User attempted to JWT login to wrong subdomain %s: %s" % (get_subdomain(request), email,)) raise JsonableError(_("Wrong subdomain")) except (jwt.DecodeError, jwt.ExpiredSignature): raise JsonableError(_("Bad JSON web token signature")) except KeyError: raise JsonableError(_("Realm not authorized for JWT login")) except UserProfile.DoesNotExist: user_profile = None return login_or_register_remote_user(request, email, user_profile, remote_user)
def test_decode_with_expiration_with_leeway(self): self.payload['exp'] = utc_timestamp() - 2 secret = 'secret' jwt_message = jwt.encode(self.payload, secret) decoded_payload, signing, header, signature = jwt.load(jwt_message) # With 3 seconds leeway, should be ok jwt.decode(jwt_message, secret, leeway=3) jwt.verify_signature(decoded_payload, signing, header, signature, secret, leeway=3) # With 1 seconds, should fail self.assertRaises( jwt.ExpiredSignature, lambda: jwt.decode(jwt_message, secret, leeway=1)) self.assertRaises( jwt.ExpiredSignature, lambda: jwt.verify_signature(decoded_payload, signing, header, signature, secret, leeway=1))
def test_decode_with_notbefore_with_leeway(self): self.payload['nbf'] = utc_timestamp() + 10 secret = 'secret' jwt_message = jwt.encode(self.payload, secret) decoded_payload, signing, header, signature = jwt.load(jwt_message) # With 13 seconds leeway, should be ok jwt.decode(jwt_message, secret, leeway=13) jwt.verify_signature(decoded_payload, signing, header, signature, secret, leeway=13) # With 1 seconds, should fail self.assertRaises( jwt.ExpiredSignature, lambda: jwt.decode(jwt_message, secret, leeway=1)) self.assertRaises( jwt.ExpiredSignature, lambda: jwt.verify_signature(decoded_payload, signing, header, signature, secret, leeway=1))
def test_decode_with_expiration(self): self.payload['exp'] = utc_timestamp() - 1 secret = 'secret' jwt_message = jwt.encode(self.payload, secret) self.assertRaises(jwt.ExpiredSignature, lambda: jwt.decode(jwt_message, secret)) decoded_payload, signing, header, signature = jwt.load(jwt_message) self.assertRaises( jwt.ExpiredSignature, lambda: jwt.verify_signature( decoded_payload, signing, header, signature, secret))
def test_decode_with_notbefore(self): self.payload['nbf'] = utc_timestamp() + 10 secret = 'secret' jwt_message = jwt.encode(self.payload, secret) self.assertRaises( jwt.ExpiredSignature, lambda: jwt.decode(jwt_message, secret)) decoded_payload, signing, header, signature = jwt.load(jwt_message) self.assertRaises( jwt.ExpiredSignature, lambda: jwt.verify_signature( decoded_payload, signing, header, signature, secret))
def identify(mw_uri, consumer_token, access_token, leeway=10.0): """ Gather's identifying information about a user via an authorized token. :Parameters: mw_uri : `str` The base URI of the MediaWiki installation. Note that the URI should end in ``"index.php"``. consumer_token : :class:`~mwoauth.ConsumerToken` A token representing you, the consumer. access_token : :class:`~mwoauth.AccessToken` A token representing an authorized user. Obtained from `complete()` leeway : `int` | `float` The number of seconds of leeway to account for when examining a tokens "issued at" timestamp. :Returns: A dictionary containing identity information. """ # Construct an OAuth auth auth = OAuth1(consumer_token.key, client_secret=consumer_token.secret, resource_owner_key=access_token.key, resource_owner_secret=access_token.secret) # Request the identity using auth r = requests.post(url=mw_uri, params={'title': "Special:OAuth/identify"}, auth=auth) # Decode json & stuff try: identity, signing_input, header, signature = jwt.load(r.content) except jwt.DecodeError as e: raise Exception("An error occurred while trying to read json " + "content: {0}".format(e)) # Ensure no downgrade in authentication if not header['alg'] == "HS256": raise Exception("Unexpected algorithm used for authentication " + "{0}, expected {1}".format("HS256", header['alg'])) # Check signature try: jwt.verify_signature(identity, signing_input, header, signature, consumer_token.secret, False) except jwt.DecodeError as e: raise Exception("Could not verify the jwt signature: {0}".format(e)) # Verify the issuer is who we expect (server sends $wgCanonicalServer) issuer = urlparse(identity['iss']).netloc expected_domain = urlparse(mw_uri).netloc if not issuer == expected_domain: raise Exception("Unexpected issuer " + "{0}, expected {1}".format(issuer, expected_domain)) # Verify we are the intended audience of this response audience = identity['aud'] if not audience == consumer_token.key: raise Exception("Unexpected audience " + "{0}, expected {1}".format(audience, expected_domain)) now = time.time() # Check that the identity was issued in the past. issued_at = float(identity['iat']) if not now >= (issued_at - leeway): raise Exception("Identity issued {0} ".format(issued_at - now) + "seconds in the future!") # Check that the identity has not yet expired expiration = float(identity['exp']) if not now <= expiration: raise Exception("Identity expired {0} ".format(expiration - now) + "seconds ago!") # Verify that the nonce matches our request one, # to avoid a replay attack authorization_header = force_unicode(r.request.headers['Authorization']) request_nonce = re.search(r'oauth_nonce="(.*?)"', authorization_header).group(1) if identity['nonce'] != request_nonce: raise Exception('Replay attack detected: {0} != {1}'.format( identity['nonce'], request_nonce)) return identity