def test_decode_jwt_invalid_key(private_key_pem): # Encode with the test private key. token = jwt.encode(_token_data("aud", "subject", "someissuer"), private_key_pem, "RS256") # Try to decode with a different public key. another_public_key = ( rsa.generate_private_key( public_exponent=65537, key_size=2048, ) .public_key() .public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ) with pytest.raises(InvalidTokenError) as ite: max_exp = exp_max_s_option(3600) decode( token, another_public_key, algorithms=["RS256"], audience="aud", issuer="someissuer", options=max_exp, leeway=60, ) assert ite.match("Signature verification failed")
def test_decode_jwt_validation( aud, iss, nbf, iat, exp, expected_exception, private_key_pem, public_key ): token = jwt.encode(_token_data(aud, "subject", iss, iat, exp, nbf), private_key_pem, "RS256") if expected_exception is not None: with pytest.raises(InvalidTokenError) as ite: max_exp = exp_max_s_option(3600) decode( token, public_key, algorithms=["RS256"], audience="someaudience", issuer="someissuer", options=max_exp, leeway=60, ) assert ite.match(expected_exception) else: max_exp = exp_max_s_option(3600) decode( token, public_key, algorithms=["RS256"], audience="someaudience", issuer="someissuer", options=max_exp, leeway=60, )
def test_decode_jwt_invalid_algorithm(private_key_pem, public_key): # Encode with the test private key. token = jwt.encode(_token_data('aud', 'subject', 'someissuer'), private_key_pem, 'RS256') # Attempt to decode but only with a different algorithm than that used. with pytest.raises(InvalidAlgorithmError) as ite: max_exp = exp_max_s_option(3600) decode(token, public_key, algorithms=['ES256'], audience='aud', issuer='someissuer', options=max_exp, leeway=60) assert ite.match('are not whitelisted')
def decode_user_jwt(self, token): """ Decodes the given JWT under the given provider and returns it. Raises an InvalidTokenError exception on an invalid token or a PublicKeyLoadException if the public key could not be loaded for decoding. """ # Find the key to use. headers = jwt.get_unverified_header(token) kid = headers.get('kid', None) if kid is None: raise InvalidTokenError('Missing `kid` header') logger.debug( 'Using key `%s`, attempting to decode token `%s` with aud `%s` and iss `%s`', kid, token, self.client_id(), self._issuer) try: return decode(token, self._get_public_key(kid), algorithms=ALLOWED_ALGORITHMS, audience=self.client_id(), issuer=self._issuer, leeway=JWT_CLOCK_SKEW_SECONDS, options=dict(require_nbf=False)) except InvalidTokenError as ite: logger.warning( 'Could not decode token `%s` for OIDC: %s. Will attempt again after ' + 'retrieving public keys.', token, ite) # Public key may have expired. Try to retrieve an updated public key and use it to decode. try: return decode(token, self._get_public_key(kid, force_refresh=True), algorithms=ALLOWED_ALGORITHMS, audience=self.client_id(), issuer=self._issuer, leeway=JWT_CLOCK_SKEW_SECONDS, options=dict(require_nbf=False)) except InvalidTokenError as ite: logger.warning( 'Could not decode token `%s` for OIDC: %s. Attempted again after ' + 'retrieving public keys.', token, ite) # Decode again with verify=False, and log the decoded token to allow for easier debugging. nonverified = decode(token, self._get_public_key(kid, force_refresh=True), algorithms=ALLOWED_ALGORITHMS, audience=self.client_id(), issuer=self._issuer, leeway=JWT_CLOCK_SKEW_SECONDS, options=dict(require_nbf=False, verify=False)) logger.debug('Got an error when trying to verify OIDC JWT: %s', nonverified) raise ite
def test_decode_jwt_invalid_key(private_key_pem): # Encode with the test private key. token = jwt.encode(_token_data('aud', 'subject', 'someissuer'), private_key_pem, 'RS256') # Try to decode with a different public key. another_public_key = RSA.generate(2048).publickey().exportKey('PEM') with pytest.raises(InvalidTokenError) as ite: max_exp = exp_max_s_option(3600) decode(token, another_public_key, algorithms=['RS256'], audience='aud', issuer='someissuer', options=max_exp, leeway=60) assert ite.match('Signature verification failed')
def _validate_jwt(encoded_jwt, jwk, service): public_key = jwtutil.jwk_dict_to_public_key(jwk) try: jwtutil.decode(encoded_jwt, public_key, algorithms=['RS256'], audience=JWT_AUDIENCE, issuer=service) except jwtutil.InvalidTokenError: logger.exception('JWT validation failure') abort(400)
def test_jwk_dict_to_public_key(private_key, private_key_pem): public_key = private_key.publickey() jwk = RSAKey(key=private_key.publickey()).serialize() converted = jwk_dict_to_public_key(jwk) # Encode with the test private key. token = jwt.encode(_token_data('aud', 'subject', 'someissuer'), private_key_pem, 'RS256') # Decode with the converted key. max_exp = exp_max_s_option(3600) decode(token, converted, algorithms=['RS256'], audience='aud', issuer='someissuer', options=max_exp, leeway=60)
def _execute_call(self, url, aud, auth=None, params=None): """ Executes a call to the external JWT auth provider. """ result = self.client.get(url, timeout=2, auth=auth, params=params) if result.status_code != 200: return (None, result.text or 'Could not make JWT auth call') try: result_data = json.loads(result.text) except ValueError: raise Exception( 'Returned JWT body for url %s does not contain JSON', url) # Load the JWT returned. encoded = result_data.get('token', '') exp_limit_options = jwtutil.exp_max_s_option(self.max_fresh_s) try: payload = jwtutil.decode(encoded, self.public_key, algorithms=['RS256'], audience=aud, issuer=self.issuer, options=exp_limit_options) return (payload, None) except jwtutil.InvalidTokenError: logger.exception('Exception when decoding returned JWT for url %s', url) return (None, 'Exception when decoding returned JWT')
def test_decode_jwt_invalid_algorithm(private_key_pem, public_key): # Encode with the test private key. token = jwt.encode(_token_data("aud", "subject", "someissuer"), private_key_pem, "RS256") # Attempt to decode but only with a different algorithm than that used. with pytest.raises(InvalidAlgorithmError) as ite: max_exp = exp_max_s_option(3600) decode( token, public_key, algorithms=["ES256"], audience="aud", issuer="someissuer", options=max_exp, leeway=60, ) assert ite.match("are not whitelisted")
def verify_build_token(token, aud, token_type, instance_keys): """Verify the JWT build token.""" try: headers = jwt.get_unverified_header(token) except jwtutil.InvalidTokenError as ite: logger.error("Invalid token reason: %s", ite) raise InvalidBuildTokenException(ite) kid = headers.get("kid", None) if kid is None: logger.error("Missing kid header on encoded JWT: %s", token) raise InvalidBuildTokenException("Missing kid header") public_key = instance_keys.get_service_key_public_key(kid) if public_key is None: logger.error( "Could not find requested service key %s with encoded JWT: %s", kid, token) raise InvalidBuildTokenException("Unknown service key") try: payload = jwtutil.decode( token, public_key, verify=True, algorithms=[ALGORITHM], audience=aud, issuer=instance_keys.service_name, leeway=JWT_CLOCK_SKEW_SECONDS, ) except jwtutil.InvalidTokenError as ite: logger.error("Invalid token reason: %s", ite) raise InvalidBuildTokenException(ite) if "sub" not in payload: raise InvalidBuildTokenException("Missing sub field in JWT") if payload["sub"] != ANONYMOUS_SUB: raise InvalidBuildTokenException("Wrong sub field in JWT") if ("context" not in payload or not payload["context"]["token_type"] or not payload["context"]["build_id"] or not payload["context"]["job_id"] or not payload["context"]["expiration"]): raise InvalidBuildTokenException("Missing context field in JWT") try: jsonschema.validate(payload["context"], BUILD_TOKEN_CONTEXT_SCHEMA) except jsonschema.ValidationError: raise InvalidBuildTokenException( "Unable to validate build token context schema: malformed context") if payload["context"]["token_type"] != token_type: raise InvalidBuildTokenException( "Build token type in JWT does not match expected type: %s" % token_type) return payload
def decode_bearer_token(bearer_token, instance_keys, config, metric_queue=None): """ decode_bearer_token decodes the given bearer token that contains both a Key ID as well as the encoded JWT and returns the decoded and validated JWT. On any error, raises an InvalidBearerTokenException with the reason for failure. """ # Decode the key ID. try: headers = jwt.get_unverified_header(bearer_token) except jwtutil.InvalidTokenError as ite: logger.exception("Invalid token reason: %s", ite) raise InvalidBearerTokenException(ite) kid = headers.get("kid", None) if kid is None: logger.error("Missing kid header on encoded JWT: %s", bearer_token) raise InvalidBearerTokenException("Missing kid header") # Find the matching public key. public_key = instance_keys.get_service_key_public_key(kid) if public_key is None: if metric_queue is not None: metric_queue.invalid_instance_key_count.Inc(labelvalues=[kid]) logger.error( "Could not find requested service key %s with encoded JWT: %s", kid, bearer_token) raise InvalidBearerTokenException("Unknown service key") # Load the JWT returned. try: expected_issuer = instance_keys.service_name audience = config["SERVER_HOSTNAME"] max_signed_s = config.get("REGISTRY_JWT_AUTH_MAX_FRESH_S", 3660) max_exp = jwtutil.exp_max_s_option(max_signed_s) payload = jwtutil.decode( bearer_token, public_key, algorithms=[ALGORITHM], audience=audience, issuer=expected_issuer, options=max_exp, leeway=JWT_CLOCK_SKEW_SECONDS, ) except jwtutil.InvalidTokenError as ite: logger.exception("Invalid token reason: %s", ite) raise InvalidBearerTokenException(ite) if not "sub" in payload: raise InvalidBearerTokenException("Missing sub field in JWT") return payload
def secscan_notification(): # If Quay is configured with a Clair V4 PSK we assume # Clair will also sign JWT's with this PSK. Therefore, # attempt jwt verification. key = app.config.get("SECURITY_SCANNER_V4_PSK", None) if key: key = base64.b64decode(key) jwt_header = request.headers.get(JWT_HEADER_NAME, "") match = TOKEN_REGEX.match(jwt_header) if match is None: logger.error("Could not find matching bearer token") abort(401) token = match.group(1) try: decode(token, key=key, algorithms=["HS256"]) except jwt.exceptions.InvalidTokenError as e: logger.error("Could not verify jwt {}".format(e)) abort(401) logger.debug("Successfully verified jwt") data = request.get_json() if data is None: logger.error("expected json request") abort(400) logger.debug("Got notification from V4 Security Scanner: %s", data) if "notification_id" not in data or "callback" not in data: abort(400) notification_id = data["notification_id"] name = ["with_id", notification_id] if not secscan_notification_queue.alive(name): secscan_notification_queue.put( name, json.dumps({"notification_id": notification_id}), ) return make_response("Okay")
def test_jwk_dict_to_public_key(private_key, private_key_pem): public_key = private_key.public_key() key_dict = jwk.dumps( public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ) converted = jwk_dict_to_public_key(key_dict) # Encode with the test private key. token = jwt.encode(_token_data("aud", "subject", "someissuer"), private_key_pem, "RS256") # Decode with the converted key. max_exp = exp_max_s_option(3600) decode( token, converted, algorithms=["RS256"], audience="aud", issuer="someissuer", options=max_exp, leeway=60, )