Exemple #1
0
    def test_RSAKey_errors(self):

        rsa_key = {
            "kty": "RSA",
            "kid": "*****@*****.**",
            "use": "sig",
            "n":
            "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
            "e": "AQAB"
        }

        with pytest.raises(JWKError):
            key = jwk.RSAKey(rsa_key, 'HS256')

        rsa_key = {
            "kty": "oct",
            "kid": "*****@*****.**",
            "use": "sig",
            "n":
            "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
            "e": "AQAB"
        }

        with pytest.raises(JWKError):
            key = jwk.RSAKey(rsa_key, 'RS256')
Exemple #2
0
    def test_invalid_hash_alg(self):
        with pytest.raises(JWKError):
            key = jwk.HMACKey(hmac_key, 'RS512')

        with pytest.raises(JWKError):
            key = jwk.RSAKey(rsa_key, 'HS512')

        with pytest.raises(JWKError):
            key = jwk.ECKey(ec_key, 'RS512')
Exemple #3
0
    def test_invalid_jwk(self):

        with pytest.raises(JWKError):
            key = jwk.HMACKey(rsa_key, 'HS256')

        with pytest.raises(JWKError):
            key = jwk.RSAKey(hmac_key, 'RS256')

        with pytest.raises(JWKError):
            key = jwk.ECKey(rsa_key, 'ES256')
Exemple #4
0
def gen_jose_rs256_key_pair():
    """Generate RS256 asymmetric key pair for usage with python-jose

    Returns a tuple (private_key, public_key_jwk_dict) which can be used
    to sign (private_key) and verify (public_key_jwk_dict) tokens.

    Example usage:

        >>> private_key, public_key_jwk_dict = gen_jose_rs256_key_pair()

        >>> t = generate_token(key=private_key, algorithm="RS256")
        >>> claims = verify_and_decode_token(t, key=public_key_jwk_dict)
        >>> claims     # doctest: +ELLIPSIS
        {...}
    """

    key = rsa.generate_private_key(
        backend=crypto_default_backend(), public_exponent=65537, key_size=4096
    )

    private_key = key.private_bytes(
        encoding=crypto_serialization.Encoding.PEM,
        format=crypto_serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=crypto_serialization.NoEncryption(),
    )

    public_key_string = (
        key.public_key()
        .public_bytes(
            crypto_serialization.Encoding.PEM,
            crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
        )
        .decode("utf8")
    )

    public_key_jwk_dict = jwk.RSAKey(
        algorithm=constants.Algorithms.RS256, key=public_key_string
    ).to_dict()

    return (private_key, public_key_jwk_dict)
def _get_pubkey(issuer, requested_kid):
    """
    Construct URL to .well-known/openid-configuration.

    Fetch the config JSON document. By spec, it is required to contain a
    `jwks_uri` key.

    Use that URL to fetch the JWKS JSON document, and decode it. Return the
    resulting data structure.

    # From the spec (https://openid.net/specs/openid-connect-core-1_0.html,
    # section 10.1.1): The verifier knows to go back to the jwks_uri location to
    # re-retrieve the keys when it sees an unfamiliar kid value.
    """

    # Try to read the data from the cache. The cache is a dictionary which is
    # thread-safe in CPython. The cache key is a combination of issuer and key
    # ID. That is, if there is just a single default key for an issuer (a key
    # without key ID) then we do not support rotation. A restart of the IAM is
    # required to pick that up. That is fine.
    try:
        return ISSUER_PUBLIC_KEYS[issuer][requested_kid]
    except KeyError:
        log.info('Public key for issuer `%s` with key ID `%s` not in cache',
                 issuer, requested_kid)
        pass

    # Note(JP): while I am sure the spec says something about leading/trailing
    # slashes I opt for making sure to not miss a slash, and to not have a
    # double slash.
    cfg_url = issuer.rstrip('/') + '/.well-known/openid-configuration'

    verify_tls = True
    if config['TESTING']:
        verify_tls = False

    # Any one of the following three lines can raise an exception, as in case of
    # transport errors, unexpected HTTP response status codes, JSON
    # deserialiation problems, and failing key lookup. Handle of them in the
    # same way.
    try:
        response = requests.get(cfg_url, verify=verify_tls)
        response.raise_for_status()
        jwks_url = response.json()['jwks_uri']
    except Exception as exc:
        log.error('Could not fetch jwks_uri from `%s`: %s', cfg_url, exc)
        raise falcon.HTTPInternalServerError(
            description='Could not fetch public key material from token issuer'
        )

    # In terms of error-handling the same as above holds true.
    try:
        response = requests.get(jwks_url, verify=verify_tls)
        response.raise_for_status()
        jwks = response.json()
    except Exception as exc:
        log.error('Could not fetch jwks_uri from `%s`: %s', cfg_url, exc)
        raise falcon.HTTPInternalServerError(
            description='Could not fetch public key material from token issuer'
        )

    # `jwks` is a Python data structure that is expected to represent a valid
    # JSON Web Key Set JSON document.

    # Note(JP): as of the JWKS spec it seems to be possible that a JWKS contains
    # more than one key where individual keys do not have a key ID. The keys can
    # then be distinguished by the key type. But even then.. to quote from RFC
    # 7517: "When "kid" values are used within a JWK Set, different keys within
    # the JWK Set SHOULD use distinct "kid" values.  (One example in which
    # different keys might use the same "kid" value is if they have different
    # "kty" (key type) values but are considered to be equivalent alternatives
    # by the application using them.)" -- so, here, plan for the simplest cases:
    # if there is no `kid` set then treat it as 'default'. Only support RSA type
    # keys. If there is more than one RSA type key without key ID in the JWKS
    # then the last one wins. This will only be a problem with exotic providers,
    # it is known to not be a problem for Auth0.
    for key_dict in jwks['keys']:
        # `kid` is optional. Use 'default' as the key ID when it is not set.
        kid = key_dict.get('kid', 'default')

        # `kty` (the key type) is required. `alg` (the intended algorithm within
        # which this key is supposed to be used) is optional. Assume RSA and
        # RS256 for starters. We can widen that to support other key types, but
        # for Auth0 support we know that this is not required.
        kty = key_dict.get('kty', None)
        if kty != 'RSA':
            continue

        # The `prepared_key` attribute exposes the `cryptography`-native public
        # key object.
        key = jwk.RSAKey(key=key_dict,
                         algorithm=jwk.ALGORITHMS.RS256).prepared_key

        log.info('Public key for issuer `%s` with key ID `%s` retrieved',
                 issuer, requested_kid)
        # Insert key into cache.
        if issuer not in ISSUER_PUBLIC_KEYS:
            ISSUER_PUBLIC_KEYS[issuer] = dict()
        ISSUER_PUBLIC_KEYS[issuer][kid] = key

    try:
        return ISSUER_PUBLIC_KEYS[issuer][requested_kid]
    except KeyError:
        log.warning(
            'Public key for issuer `%s` with key ID `%s` could not be obtained',
            issuer, requested_kid)
        # Let this exception terminate request handling, send an Internal Server
        # Error to the client.
        raise