예제 #1
0
 def test_x5c_decoding(self):
     from josepy.jws import Header
     header = Header(x5c=(CERT, CERT))
     jobj = header.to_partial_json()
     cert_asn1 = OpenSSL.crypto.dump_certificate(
         OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)
     cert_b64 = base64.b64encode(cert_asn1)
     self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]})
     self.assertEqual(header, Header.from_json(jobj))
     jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1)
     self.assertRaises(errors.DeserializationError, Header.from_json, jobj)
예제 #2
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        response_jwks = requests.get(
            self.OIDC_OP_JWKS_ENDPOINT,
            verify=self.get_settings('OIDC_VERIFY_SSL', True),
            timeout=self.get_settings('OIDC_TIMEOUT', None),
            proxies=self.get_settings('OIDC_PROXY', None))
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if jwk['kid'] != smart_text(header.kid):
                continue
            if 'alg' in jwk and jwk['alg'] != smart_text(header.alg):
                raise SuspiciousOperation('alg values do not match.')
            key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
예제 #3
0
 def test_from_json(self):
     from josepy.jws import Header
     from josepy.jws import Signature
     self.assertEqual(
         Signature(signature=b'foo', header=Header(alg=jwa.RS256)),
         Signature.from_json(
             {'signature': 'Zm9v', 'header': {'alg': 'RS256'}}))
예제 #4
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the jwks endpoint of the identity
        provider."""
        key = self.cache.get(self.OIDC_OP_JWKS_ENDPOINT)
        if key:
            return key

        response_jwks = requests.get(self.OIDC_OP_JWKS_ENDPOINT,
                                     verify=import_from_settings(
                                         'OIDC_VERIFY_SSL', True))
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if (jwk['alg'] == smart_text(header.alg)
                    and jwk['kid'] == smart_text(header.kid)):
                key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')

        self.cache.set(self.OIDC_OP_JWKS_ENDPOINT, key, 3600)
        return key
예제 #5
0
파일: drf.py 프로젝트: krtb/glam
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        jwks = get_jwks()

        # Compute the current header from the given token to find a match
        try:
            jws = JWS.from_compact(token)
        except DeserializationError as e:
            raise exceptions.AuthenticationFailed(e)

        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks["keys"]:
            if jwk["kid"] != smart_text(header.kid):
                continue
            if "alg" in jwk and jwk["alg"] != smart_text(header.alg):
                raise exceptions.AuthenticationFailed(
                    "alg values do not match.")
            key = jwk
        if key is None:
            raise exceptions.AuthenticationFailed(
                "Could not find a valid JWKS.")
        return key
예제 #6
0
class HeaderTest(unittest.TestCase):
    """Tests for josepy.jws.Header."""

    def setUp(self):
        from josepy.jws import Header
        self.header1 = Header(jwk='foo')
        self.header2 = Header(jwk='bar')
        self.crit = Header(crit=('a', 'b'))
        self.empty = Header()

    def test_add_non_empty(self):
        from josepy.jws import Header
        self.assertEqual(Header(jwk='foo', crit=('a', 'b')),
                         self.header1 + self.crit)

    def test_add_empty(self):
        self.assertEqual(self.header1, self.header1 + self.empty)
        self.assertEqual(self.header1, self.empty + self.header1)

    def test_add_overlapping_error(self):
        self.assertRaises(TypeError, self.header1.__add__, self.header2)

    def test_add_wrong_type_error(self):
        self.assertRaises(TypeError, self.header1.__add__, 'xxx')

    def test_crit_decode_always_errors(self):
        from josepy.jws import Header
        self.assertRaises(errors.DeserializationError, Header.from_json,
                          {'crit': ['a', 'b']})

    def test_x5c_decoding(self):
        from josepy.jws import Header
        header = Header(x5c=(CERT, CERT))
        jobj = header.to_partial_json()
        cert_asn1 = OpenSSL.crypto.dump_certificate(
            OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)
        cert_b64 = base64.b64encode(cert_asn1)
        self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]})
        self.assertEqual(header, Header.from_json(jobj))
        jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1)
        self.assertRaises(errors.DeserializationError, Header.from_json, jobj)

    def test_find_key(self):
        self.assertEqual('foo', self.header1.find_key())
        self.assertEqual('bar', self.header2.find_key())
        self.assertRaises(errors.Error, self.crit.find_key)
예제 #7
0
    async def jwt_verify_and_decode(
        self,
        id_token: str,
        jwks_endpoint: str,
        verify: bool = True,
        audience: str = None,
    ) -> Dict[str, str]:
        """
        Decodes the JSON Web Token (JWT) sent from the platform. The JWT should contain claims
        that represent properties associated with the request. This method also verifies the JWT's
        signature using the platform's public key.

        Args:
          id_token: JWT token issued by the platform
          jwks_endpoint: JSON web key (publick key) endpoint
          verify: verify whether or not to verify JWT when decoding. Defaults to True.
          audience: the platform's OAuth2 Audience (aud). This value usually coincides with the
            token endpoint for the platform (LMS) such as https://my.lms.domain/login/oauth2/token

        Returns:
          Decoded dictionary that represents the k/v's included with the JWT
        """
        if verify is False:
            unverified_token = jwt.decode(id_token,
                                          options={"verify_signature": False})
            self.log.debug(
                f"JWK verification is off, returning token {unverified_token}")
            return unverified_token

        jws = JWS.from_compact(id_token)
        self.log.debug(f"Retrieving matching jws {jws}")
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)
        self.log.debug(f"Header from decoded jwt {header}")

        key_from_jwks = await self._retrieve_matching_jwk(
            jwks_endpoint, verify, header.kid)
        self.log.debug(
            f"Returning decoded jwt with token {id_token} key {key_from_jwks} and verify {verify}"
        )

        return jwt.decode(
            id_token,
            key=key_from_jwks,
            audience=audience,
            options={"verify_signature": False},
        )
예제 #8
0
    async def jwt_verify_and_decode(self,
                                    id_token: str,
                                    jwks_endpoint: str,
                                    verify: bool = True,
                                    audience: str = None) -> Dict[str, str]:
        """
        Decodes the JSON Web Token (JWT) sent from the platform. The JWT should contain claims
        that represent properties associated with the request. This method implicitly verifies the JWT's
        signature using the platform's public key.

        Args:
          id_token: JWT token issued by the platform
          jwks_endpoint: JSON web key (publick key) endpoint
          verify: verify whether or not to verify JWT when decoding. Defaults to True.
          audience: the platform's OAuth2 Audience (aud). This value usually coincides with the
            token endpoint for the platform (LMS) such as https://my.lms.domain/login/oauth2/token
        """
        if verify is False:
            self.log.debug('JWK verification is off, returning token %s' %
                           jwt.decode(id_token, verify=False))
            return jwt.decode(id_token, verify=False)
        retrieved_jwks = await self._retrieve_matching_jwk(
            jwks_endpoint, verify)
        jws = JWS.from_compact(bytes(id_token, 'utf-8'))
        self.log.debug('Retrieving matching jws %s' % jws)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)
        self.log.debug('Header from decoded jwt %s' % header)
        key = None
        for jwk in retrieved_jwks['keys']:
            if jwk['kid'] != header.kid:
                continue
            key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))
            self.log.debug('Get keys from jwks dict  %s' % key)
        if key is None:
            self.log.debug('Key is None, returning None')
            return None
        self.log.debug(
            'Returning decoded jwt with token %s key %s and verify %s' %
            (id_token, key, verify))
        return jwt.decode(id_token, key=key, verify=False, audience=audience)
예제 #9
0
파일: auth.py 프로젝트: cloudera/hue
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP."""
        response_jwks = requests.get(
            self.OIDC_OP_JWKS_ENDPOINT,
            verify=import_from_settings('OIDC_VERIFY_SSL', True)
        )
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        for jwk in jwks['keys']:
            if jwk['alg'] == smart_text(header.alg) and jwk['kid'] == smart_text(header.kid):
                key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
예제 #10
0
    def retrieve_matching_jwk(self, token):
        """Get the signing key by exploring the JWKS endpoint of the OP.

        Overriding original method because KeyCloak isn't providing the key id
        in the token.  Since there's only one jwks key, try using that key
        to verify the token's signature.

        Args:
            token

        Returns:
            Public signing key for the KeyCloak realm.

        Raises:
            (SuspiciousOperation)
        """
        response_jwks = requests.get(self.OIDC_OP_JWKS_ENDPOINT,
                                     verify=self.get_settings(
                                         'OIDC_VERIFY_SSL', True))
        response_jwks.raise_for_status()
        jwks = response_jwks.json()

        # Compute the current header from the given token to find a match
        jws = JWS.from_compact(token)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)

        key = None
        num_keys = len(jwks['keys'])
        for jwk in jwks['keys']:
            # If there's only one key, then try it even if the key id doesn't
            # match or is None.
            if jwk['kid'] != smart_text(header.kid) and num_keys > 1:
                continue
            if 'alg' in jwk and jwk['alg'] != smart_text(header.alg):
                raise SuspiciousOperation('alg values do not match.')
            key = jwk
        if key is None:
            raise SuspiciousOperation('Could not find a valid JWKS.')
        return key
예제 #11
0
    async def jwt_verify_and_decode(
        self,
        id_token: str,
        jwks_endpoint: str,
        verify: bool = True,
        audience: str = None,
    ) -> Dict[str, str]:
        """
        Decodes the JSON Web Token (JWT) sent from the platform. The JWT should contain claims
        that represent properties associated with the request. This method implicitly verifies the JWT's
        signature using the platform's public key.

        Args:
          id_token: JWT token issued by the platform
          jwks_endpoint: JSON web key (publick key) endpoint
          verify: verify whether or not to verify JWT when decoding. Defaults to True.
          audience: the platform's OAuth2 Audience (aud). This value usually coincides with the
            token endpoint for the platform (LMS) such as https://my.lms.domain/login/oauth2/token
        """
        if verify is False:
            self.log.debug("JWK verification is off, returning token %s" %
                           jwt.decode(id_token, verify=False))
            return jwt.decode(id_token, verify=False)

        jws = JWS.from_compact(id_token)
        self.log.debug("Retrieving matching jws %s" % jws)
        json_header = jws.signature.protected
        header = Header.json_loads(json_header)
        self.log.debug("Header from decoded jwt %s" % header)

        key_from_jwks = await self._retrieve_matching_jwk(
            jwks_endpoint, verify, header.kid)
        self.log.debug(
            "Returning decoded jwt with token %s key %s and verify %s" %
            (id_token, key_from_jwks, verify))

        return jwt.decode(id_token,
                          key=key_from_jwks,
                          verify=False,
                          audience=audience)
예제 #12
0
def verify_jws(token, client_id, openid_configuration):
    jws = JWS.from_compact(token.encode("ascii"))
    json_header = jws.signature.protected
    header = Header.json_loads(json_header)

    # alg
    alg = jws.signature.combined.alg.name.upper()
    if "NONE" in alg:
        raise SuspiciousOperation("ID token is not signed")
    if alg not in (alg.upper() for alg in openid_configuration["id_token_signing_alg_values_supported"]):
        raise SuspiciousOperation("Unexpected ID token signature algorithm")

    # retrieve signature key
    # TODO cache
    jwks_response = requests.get(openid_configuration["jwks_uri"], headers={"Accept": "application/json"})
    jwks_response.raise_for_status()
    jwk = None
    for jwk_json in jwks_response.json()["keys"]:
        if jwk_json["kid"] == header.kid:
            jwk = JWK.from_json(jwk_json)
            break
    if not jwk:
        raise SuspiciousOperation("Could not find ID token signing key")

    # verify signature
    if not jws.verify(jwk):
        raise SuspiciousOperation("Invalid ID token signature")

    payload = json.loads(jws.payload.decode('utf-8'))

    # iss
    if payload.get("iss") != openid_configuration["issuer"]:
        raise SuspiciousOperation("Invalid ID token 'iss'")

    # aud
    if payload.get("aud") != client_id:
        raise SuspiciousOperation("Invalid ID token 'aud'")

    timestamp = int(time.time())

    # nbf
    nbf = payload.get("nbf")
    if nbf is not None:
        try:
            nbf = int(nbf)
        except (TypeError, ValueError):
            raise SuspiciousOperation("Invalid ID token 'nbf'")
        if timestamp < nbf - TIMESTAMP_LEEWAY:
            raise SuspiciousOperation("ID token not valid yet")

    # exp
    exp = payload.get("exp")
    if exp is not None:
        try:
            exp = int(exp)
        except (TypeError, ValueError):
            raise SuspiciousOperation("Invalid ID token 'exp'")
        if timestamp > exp + TIMESTAMP_LEEWAY:
            raise SuspiciousOperation("ID token has expired")

    return payload
예제 #13
0
 def test_add_non_empty(self):
     from josepy.jws import Header
     self.assertEqual(Header(jwk='foo', crit=('a', 'b')),
                      self.header1 + self.crit)
예제 #14
0
 def setUp(self):
     from josepy.jws import Header
     self.header1 = Header(jwk='foo')
     self.header2 = Header(jwk='bar')
     self.crit = Header(crit=('a', 'b'))
     self.empty = Header()
예제 #15
0
            self.log.debug('JWK verification is off, returning token')

            return token

        if isinstance(id_token, str):

            id_token = bytes(id_token, encoding='utf-8')

        jws = JWS.from_compact(id_token)

        self.log.debug('Retrieving matching jws %s' % jws)

        json_header = jws.signature.protected

        header = Header.json_loads(json_header)

        self.log.debug('Header from decoded jwt %s' % header)

        key_from_jwks = await self._retrieve_matching_jwk(jwks_endpoint,
                                                          header.kid,
                                                          verify=verify)

        self.log.debug(
            'Returning decoded jwt with token %s key %s and verify %s' %
            (id_token, key_from_jwks, verify))

        return jwt.decode(id_token,
                          key=str(key_from_jwks),
                          verify=False,
                          audience=audience,