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()
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.' )
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
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
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
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()
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
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))
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.'
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
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,
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
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
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))
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.'