def test_get_jwt_authority_invalid_input():
    jwt_bundle = JwtBundle(trust_domain, authorities)

    with pytest.raises(ArgumentError) as exception:
        jwt_bundle.get_jwt_authority('')

    assert str(exception.value) == 'key_id cannot be empty.'
def test_get_jwt_authority_empty_authority_dict():
    invalid_authorities = None
    jwt_bundle = JwtBundle(trust_domain, invalid_authorities)

    response = jwt_bundle.get_jwt_authority(key_id='p1')

    assert response is None
def test_get():
    jwt_bundle = JwtBundle(trust_domain_1, authorities)
    jwt_bundle_set = JwtBundleSet({trust_domain_1: jwt_bundle})

    res = jwt_bundle_set.get(trust_domain_1)

    assert res == jwt_bundle
    assert res.trust_domain() == jwt_bundle.trust_domain()
Example #4
0
def test_parse_corrupted_key_missing_key_id():
    with pytest.raises(ParseJWTBundleError) as exception:
        JwtBundle.parse(trust_domain, JWKS_MISSING_KEY_ID)

    assert (
        str(exception.value) ==
        'Error parsing JWT bundle: Error adding authority from JWKS: keyID cannot be empty.'
    )
Example #5
0
def test_parse_invalid_bytes(test_bytes):
    with pytest.raises(ParseJWTBundleError) as exception:
        JwtBundle.parse(trust_domain, test_bytes)

    assert (
        str(exception.value) ==
        'Error parsing JWT bundle: Cannot parse jwks. bundle_bytes does not represent a valid jwks.'
    )
def test_put_replace_bundle_for_trust_domain():
    jwt_bundle = JwtBundle(trust_domain_1, authorities)
    jwt_bundle_set = JwtBundleSet({trust_domain_1: jwt_bundle})

    assert len(jwt_bundle_set._bundles) == 1
    assert jwt_bundle_set._bundles[trust_domain_1] == jwt_bundle

    new_jwt_bundle = JwtBundle(trust_domain_1, authorities)
    jwt_bundle_set.put(new_jwt_bundle)

    assert len(jwt_bundle_set._bundles) == 1
    assert jwt_bundle_set._bundles[trust_domain_1] == new_jwt_bundle
Example #7
0
def test_parse_bundle_bytes_invalid_key(mocker):
    mocker.patch(
        'pyspiffe.bundle.jwt_bundle.jwt_bundle.PyJWKSet.from_json',
        side_effect=InvalidKeyError('Invalid Key'),
        autospect=True,
    )

    with pytest.raises(ParseJWTBundleError) as exception:
        JwtBundle.parse(trust_domain, JWKS_MISSING_X)

    assert (
        str(exception.value) ==
        'Error parsing JWT bundle: Cannot parse jwks from bundle_bytes: Invalid Key.'
    )
def test_create_jwt_bundle_set():
    jwt_bundle_1 = JwtBundle(trust_domain_1, authorities)
    jwt_bundle_2 = JwtBundle(trust_domain_2, authorities)

    fake_bundles = {trust_domain_1: jwt_bundle_1, trust_domain_2: jwt_bundle_2}

    jwt_bundle_set = JwtBundleSet(fake_bundles)

    # check that the bundle was copied
    assert jwt_bundle_set._bundles is not fake_bundles
    assert len(jwt_bundle_set._bundles) == len(fake_bundles.keys())
    assert list(jwt_bundle_set._bundles.keys())[0].name() == trust_domain_1.name()
    assert jwt_bundle_set._bundles[trust_domain_1] == jwt_bundle_1
    assert list(jwt_bundle_set._bundles.keys())[1].name() == trust_domain_2.name()
    assert jwt_bundle_set._bundles[trust_domain_2] == jwt_bundle_2
def test_get_non_existing_trust_domain():
    jwt_bundle = JwtBundle(trust_domain_1, authorities)
    jwt_bundle_set = JwtBundleSet({trust_domain_1: jwt_bundle})

    res = jwt_bundle_set.get(trust_domain_2)

    assert res is None
Example #10
0
    def _create_td_jwt_bundle_dict(
        jwt_bundle_response: workload_pb2.JWTBundlesResponse,
    ) -> Dict[TrustDomain, JwtBundle]:
        jwt_bundles = {}
        for td, jwk_set in jwt_bundle_response.bundles.items():
            jwt_bundles[TrustDomain.parse(td)] = JwtBundle.parse(
                TrustDomain.parse(td), jwk_set)

        return jwt_bundles
Example #11
0
    def put(self, jwt_bundle: JwtBundle):
        """Adds a new bundle into the set.

        If a bundle already exists for the trust domain, the existing bundle is
        replaced.

        Args:
            jwt_bundle: The new JwtBundle to add.
        """
        with self.lock:
            self._bundles[jwt_bundle.trust_domain()] = jwt_bundle
def test_put_bundle_on_empty_set():
    jwt_bundle_set = JwtBundleSet({})

    assert len(jwt_bundle_set._bundles) == 0

    jwt_bundle = JwtBundle(trust_domain_1, authorities)

    jwt_bundle_set.put(jwt_bundle)

    assert len(jwt_bundle_set._bundles) == 1
    assert list(jwt_bundle_set._bundles.keys())[0].name() == trust_domain_1.name()
Example #13
0
def test_parse_and_validate_valid_token_EC():
    ec_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
    jwt_bundle = JwtBundle(DEFAULT_TRUST_DOMAIN,
                           {'kid_ec': ec_key.public_key()})

    ec_key_pem, _ = get_keys_pems(ec_key)
    token = create_jwt(ec_key_pem, 'kid_ec', alg='ES512')
    jwt_svid = JwtSvid.parse_and_validate(token, jwt_bundle, ['spire'])
    assert jwt_svid.audience == DEFAULT_AUDIENCE
    assert str(jwt_svid.spiffe_id) == DEFAULT_SPIFFE_ID
    assert jwt_svid.expiry == DEFAULT_EXPIRY
    assert jwt_svid.token == token
Example #14
0
    def parse_and_validate(
        cls, token: str, jwt_bundle: JwtBundle, audience: List[str]
    ) -> 'JwtSvid':
        """Parses and validates a JWT-SVID token and returns an instance of JwtSvid.

        The JWT-SVID signature is verified using the JWT bundle source.

        Args:
            token: A token as a string that is parsed and validated.
            jwt_bundle: An instance of JwtBundle that provides the JWT authorities to verify the signature.
            audience: A list of strings used to validate the 'aud' claim.

        Returns:
            An instance of JwtSvid with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry
            from 'exp' claim.

        Raises:
            JwtSvidError:   When the token expired or the expiration claim is missing,
                            when the algorithm is not supported, when the header 'kid' is missing,
                            when the signature cannot be verified, or
                            when the 'aud' claim has an audience that is not in the audience list provided as parameter.
            ValueError:     When the token is blank or cannot be parsed.
            BundleNotFoundError:    If the bundle for the trust domain of the spiffe id from the 'sub'
                                    cannot be found the jwt_bundle_source.
            AuthorityNotFoundError: If the authority cannot be found in the bundle using the value from the 'kid' header.
            InvalidTokenError: In case token is malformed and fails to decode.
        """
        if not token:
            raise ValueError(INVALID_INPUT_ERROR.format('token cannot be empty'))

        if not jwt_bundle:
            raise ValueError(INVALID_INPUT_ERROR.format('jwt_bundle cannot be empty'))
        try:
            token_header = jwt.get_unverified_header(token)
            validator = JwtSvidValidator()
            validator.validate_header(token_header)
            signing_key = jwt_bundle.find_jwt_authority(token_header['kid'])
            claims = jwt.decode(
                token,
                algorithms=token_header['alg'],
                key=signing_key,
                audience=audience,
                options={
                    'verify_signature': True,
                    'verify_aud': True,
                    'verify_exp': True,
                },
            )
            # TODO:validate required claims
            spiffe_ID = SpiffeId.parse(claims['sub'])
            return JwtSvid(spiffe_ID, claims['aud'], claims['exp'], claims, token)
        except PyJWTError as err:
            raise InvalidTokenError(str(err))
Example #15
0
def test_parse_and_validate_invalid_kid_mismatch():
    rsa_key2 = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    jwt_bundle = JwtBundle(
        DEFAULT_TRUST_DOMAIN,
        {
            'kid1': DEFAULT_KEY.public_key(),
            'kid10': rsa_key2.public_key()
        },
    )
    token = create_jwt(kid='kid10')

    with pytest.raises(InvalidTokenError) as exception:
        JwtSvid.parse_and_validate(token, jwt_bundle, ['spire'])
    assert str(exception.value) == 'Signature verification failed.'
Example #16
0
def test_parse_and_validate_valid_token_multiple_keys_bundle():
    ec_key = ec.generate_private_key(ec.SECP521R1(), default_backend())
    jwt_bundle = JwtBundle(
        DEFAULT_TRUST_DOMAIN,
        {
            'kid_rsa': DEFAULT_KEY.public_key(),
            'kid_ec': ec_key.public_key()
        },
    )
    ec_key_pem, _ = get_keys_pems(ec_key)

    token = create_jwt(ec_key_pem, kid='kid_ec', alg='ES512')
    jwt_svid1 = JwtSvid.parse_and_validate(token, jwt_bundle, ['spire'])
    assert jwt_svid1.audience == DEFAULT_AUDIENCE
    assert str(jwt_svid1.spiffe_id) == DEFAULT_SPIFFE_ID
    assert jwt_svid1.expiry == DEFAULT_EXPIRY
    assert jwt_svid1.token == token

    token2 = create_jwt(kid='kid_rsa')
    jwt_svid2 = JwtSvid.parse_and_validate(token2, jwt_bundle, ['spire'])
    assert jwt_svid2.audience == DEFAULT_AUDIENCE
    assert str(jwt_svid2.spiffe_id) == DEFAULT_SPIFFE_ID
    assert jwt_svid2.expiry == DEFAULT_EXPIRY
    assert jwt_svid2.token == token2
Example #17
0
    JwtSvidError,
    InvalidTokenError,
    MissingClaimError,
)
from pyspiffe.bundle.jwt_bundle.exceptions import AuthorityNotFoundError
from test.svid.test_utils import (
    get_keys_pems,
    create_jwt,
    DEFAULT_SPIFFE_ID,
    DEFAULT_AUDIENCE,
    DEFAULT_KEY,
    DEFAULT_TRUST_DOMAIN,
    DEFAULT_EXPIRY,
)

JWT_BUNDLE = JwtBundle(DEFAULT_TRUST_DOMAIN,
                       {'kid1': DEFAULT_KEY.public_key()})
"""
    parse_insecure tests
"""


@pytest.mark.parametrize(
    'test_input_token,test_input_audience, expected',
    [
        ('', [], INVALID_INPUT_ERROR.format('token cannot be empty.')),
        ('', None, INVALID_INPUT_ERROR.format('token cannot be empty.')),
        (None, [], INVALID_INPUT_ERROR.format('token cannot be empty.')),
        (None, None, INVALID_INPUT_ERROR.format('token cannot be empty.')),
    ],
)
def test_parse_insecure_invalid_input(test_input_token, test_input_audience,
Example #18
0
def test_parse(test_bytes, expected_authorities):
    jwt_bundle = JwtBundle.parse(trust_domain, test_bytes)

    assert jwt_bundle
    assert len(jwt_bundle.jwt_authorities()) == expected_authorities
def test_create_jwt_bundle():
    jwt_bundle = JwtBundle(trust_domain, authorities)

    assert jwt_bundle.trust_domain() == trust_domain
    assert len(jwt_bundle.jwt_authorities().keys()) == len(authorities.keys())
def test_create_jwt_bundle_no_trust_domain():
    with pytest.raises(JwtBundleError) as exc_info:
        JwtBundle(None, authorities)

    assert str(exc_info.value) == 'Trust domain cannot be empty.'
def test_create_jwt_bundle_no_authorities():
    jwt_bundle = JwtBundle(trust_domain, None)

    assert jwt_bundle.trust_domain() == trust_domain
    assert isinstance(jwt_bundle.jwt_authorities(), dict)
    assert len(jwt_bundle.jwt_authorities().keys()) == 0
def test_get_jwt_authority_valid_input():
    jwt_bundle = JwtBundle(trust_domain, authorities)

    authority_key = jwt_bundle.get_jwt_authority('kid2')

    assert rsa_key == authority_key
Example #23
0
def test_parse_missing_bundle_bytes(test_bundle_bytes):
    with pytest.raises(ArgumentError) as exception:
        JwtBundle.parse(trust_domain, test_bundle_bytes)

    assert str(exception.value) == 'Bundle bytes cannot be empty.'
def test_get_jwt_authority_invalid_key_id_not_found():
    jwt_bundle = JwtBundle(trust_domain, authorities)

    response = jwt_bundle.get_jwt_authority('p0')

    assert response is None
Example #25
0
    def parse_and_validate(cls, token: str, jwt_bundle: JwtBundle,
                           audience: List[str]) -> 'JwtSvid':
        """Parses and validates a JWT-SVID token and returns an instance of JwtSvid.

        The JWT-SVID signature is verified using the JWT bundle source.

        Args:
            token: A token as a string that is parsed and validated.
            jwt_bundle: An instance of JwtBundle that provides the JWT authorities to verify the signature.
            audience: A list of strings used to validate the 'aud' claim.

        Returns:
            An instance of JwtSvid with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry
            from 'exp' claim.

        Raises:
            JwtSvidError:   When the token expired or the expiration claim is missing,
                            when the algorithm is not supported, when the header 'kid' is missing,
                            when the signature cannot be verified, or
                            when the 'aud' claim has an audience that is not in the audience list provided as parameter.
            ArgumentError:     When the token is blank or cannot be parsed.
            BundleNotFoundError:    If the bundle for the trust domain of the spiffe id from the 'sub'
                                    cannot be found the jwt_bundle_source.
            AuthorityNotFoundError: If the authority cannot be found in the bundle using the value from the 'kid' header.
            InvalidTokenError: In case token is malformed and fails to decode.
        """
        if not token:
            raise ArgumentError(
                INVALID_INPUT_ERROR.format('token cannot be empty'))

        if not jwt_bundle:
            raise ArgumentError(
                INVALID_INPUT_ERROR.format('jwt_bundle cannot be empty'))
        try:
            header_params = jwt.get_unverified_header(token)
            validator = JwtSvidValidator()
            validator.validate_header(header_params)
            key_id = header_params.get('kid')
            signing_key = jwt_bundle.get_jwt_authority(key_id)
            if not signing_key:
                raise AuthorityNotFoundError(key_id)

            public_key_pem = signing_key.public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
            ).decode('UTF-8')

            claims = jwt.decode(
                token,
                algorithms=header_params.get('alg'),
                key=public_key_pem,
                audience=audience,
                options={
                    'verify_signature': True,
                    'verify_aud': True,
                    'verify_exp': True,
                },
            )

            spiffe_id = SpiffeId.parse(claims.get('sub', None))

            return JwtSvid(spiffe_id, claims['aud'], claims['exp'], claims,
                           token)
        except PyJWTError as err:
            raise InvalidTokenError(str(err))
        except ArgumentError as value_err:
            raise InvalidTokenError(str(value_err))
Example #26
0
def test_parse_invalid_trust_domain(test_trust_domain):
    with pytest.raises(ArgumentError) as exception:
        JwtBundle.parse(test_trust_domain, JWKS_1_EC_KEY)

    assert str(exception.value) == 'Trust domain is missing.'