Beispiel #1
0
def request_object_encryption(msg, service_context, **kwargs):
    """
    Created an encrypted JSON Web token with *msg* as body.

    :param msg: The mesaqg
    :param service_context:
    :param kwargs:
    :return:
    """
    try:
        encalg = kwargs["request_object_encryption_alg"]
    except KeyError:
        try:
            encalg = service_context.get('behaviour')[
                "request_object_encryption_alg"]
        except KeyError:
            return msg

    if not encalg:
        return msg

    try:
        encenc = kwargs["request_object_encryption_enc"]
    except KeyError:
        try:
            encenc = service_context.get('behaviour')[
                "request_object_encryption_enc"]
        except KeyError:
            raise MissingRequiredAttribute(
                "No request_object_encryption_enc specified")

    if not encenc:
        raise MissingRequiredAttribute(
            "No request_object_encryption_enc specified")

    _jwe = JWE(msg, alg=encalg, enc=encenc)
    _kty = alg2keytype(encalg)

    try:
        _kid = kwargs["enc_kid"]
    except KeyError:
        _kid = ""

    if "target" not in kwargs:
        raise MissingRequiredAttribute("No target specified")

    if _kid:
        _keys = service_context.keyjar.get_encrypt_key(_kty,
                                                       issuer_id=kwargs["target"],
                                                       kid=_kid)
        _jwe["kid"] = _kid
    else:
        _keys = service_context.keyjar.get_encrypt_key(_kty,
                                                       issuer_id=kwargs["target"])

    return _jwe.encrypt(_keys)
def verify_id_token(msg, check_hash=False, claim='id_token', **kwargs):
    # Try to decode the JWT, checks the signature
    args = {}
    for arg in ID_TOKEN_VERIFY_ARGS:
        try:
            args[arg] = kwargs[arg]
        except KeyError:
            pass

    _jws = jws_factory(msg[claim])
    if not _jws:
        raise ValueError('{} not a signed JWT'.format(claim))

    if _jws.jwt.headers['alg'] == 'none':
        try:
            _allow_none = kwargs['allow_sign_alg_none']
        except KeyError:
            logger.info('Signing algorithm None not allowed')
            return False
        else:
            if not _allow_none:
                logger.info('Signing algorithm None not allowed')
                return False

    _body = _jws.jwt.payload()
    if 'keyjar' in kwargs:
        try:
            if _body['iss'] not in kwargs['keyjar']:
                raise ValueError('Unknown issuer')
        except KeyError:
            raise MissingRequiredAttribute('iss')

    idt = IdToken().from_jwt(str(msg[claim]), **args)
    if not idt.verify(**kwargs):
        return False

    if check_hash:
        _alg = idt.jws_header["alg"]
        hfunc = "HS" + _alg[-3:]

        if "access_token" in msg:
            if "at_hash" not in idt:
                raise MissingRequiredAttribute("Missing at_hash property", idt)
            if idt["at_hash"] != left_hash(msg["access_token"], hfunc):
                raise AtHashError("Failed to verify access_token hash", idt)

        if "code" in msg:
            if "c_hash" not in idt:
                raise MissingRequiredAttribute("Missing c_hash property", idt)
            if idt["c_hash"] != left_hash(msg["code"], hfunc):
                raise CHashError("Failed to verify code hash", idt)

    msg[verified_claim_name(claim)] = idt
    logger.info('Verified {}: {}'.format(claim, idt.to_dict()))

    return True
Beispiel #3
0
    def verify(self, **kwargs):
        super(ProviderConfigurationResponse, self).verify(**kwargs)

        if "scopes_supported" in self:
            if "openid" not in self["scopes_supported"]:
                raise MissingRequiredValue
            for scope in self["scopes_supported"]:
                check_char_set(scope, SCOPE_CHARSET)

        parts = urlparse(self["issuer"])
        if 'allow_http' in kwargs:
            pass
        elif parts.scheme != "https":
            raise SchemeError("Not HTTPS")

        if "RS256" not in self["id_token_signing_alg_values_supported"]:
            raise ValueError(
                'RS256 missing from id_token_signing_alg_values_supported')

        if not parts.query and not parts.fragment:
            pass
        else:
            raise ValueError('Issuer ID invalid')

        if any("code" in rt for rt in self["response_types_supported"]
               ) and "token_endpoint" not in self:
            raise MissingRequiredAttribute("token_endpoint")

        return True
Beispiel #4
0
    def verify(self, **kwargs):
        super(RegistrationRequest, self).verify(**kwargs)

        if "initiate_login_uri" in self:
            if not self["initiate_login_uri"].startswith("https:"):
                raise ValueError("Wrong scheme")

        for param in [
            "request_object_encryption",
            "id_token_encrypted_response",
            "userinfo_encrypted_response",
        ]:
            alg_param = "%s_alg" % param
            enc_param = "%s_enc" % param
            if alg_param in self:
                if enc_param not in self:
                    self[enc_param] = "A128CBC-HS256"

            # both or none
            if enc_param in self:
                if alg_param not in self:
                    raise MissingRequiredAttribute("alg_param")

        if "token_endpoint_auth_signing_alg" in self:
            if self["token_endpoint_auth_signing_alg"] == "none":
                raise ValueError('"none" not allowed')

        return True
Beispiel #5
0
def pick_redirect_uri(context,
                      request_args: Optional[Union[Message, dict]] = None,
                      response_type: Optional[str] = ''):
    if request_args is None:
        request_args = {}

    if 'redirect_uri' in request_args:
        return request_args["redirect_uri"]

    if context.redirect_uris:
        redirect_uri = context.redirect_uris[0]
    elif context.callback:
        if not response_type:
            _conf_resp_types = context.behaviour.get('response_types', [])
            response_type = request_args.get('response_type')
            if not response_type and _conf_resp_types:
                response_type = _conf_resp_types[0]

        _response_mode = request_args.get('response_mode')

        if _response_mode == 'form_post' or response_type == ["form_post"]:
            redirect_uri = context.callback['form_post']
        elif response_type == 'code' or response_type == ["code"]:
            redirect_uri = context.callback['code']
        else:
            redirect_uri = context.callback['implicit']

        logger.debug(
            f"pick_redirect_uris: response_type={response_type}, response_mode={_response_mode}, "
            f"redirect_uri={redirect_uri}")
    else:
        logger.error("No redirect_uri")
        raise MissingRequiredAttribute('redirect_uri')

    return redirect_uri
    def verify(self, **kwargs):
        super(AccessTokenRequest, self).verify(**kwargs)

        if "device_code" in self:
            # then both client_id and grant_type MUST be present
            for claim in ["grant_type", "client_id"]:
                if claim not in self:
                    raise MissingRequiredAttribute(claim)
Beispiel #7
0
    def verify(self, **kwargs):
        super(OauthClientInformationResponse, self).verify(**kwargs)

        if "client_secret" in self:
            if "client_secret_expires_at" not in self:
                raise MissingRequiredAttribute(
                    "client_secret_expires_at is a MUST if client_secret is present"
                )
    def verify(self, **kwargs):
        """
        Make sure all the required values are there and that the values are
        of the correct type
        """
        _spec = self.c_param
        try:
            _allowed = self.c_allowed_values
        except KeyError:
            _allowed = {}

        for (attribute, (typ, required, _, _, na)) in _spec.items():
            if attribute == "*":
                continue

            try:
                val = self._dict[attribute]
            except KeyError:
                if required:
                    raise MissingRequiredAttribute("%s" % attribute)
                continue
            else:
                if typ == bool:
                    pass
                elif not val:
                    if required:
                        raise MissingRequiredAttribute("%s" % attribute)
                    continue

            try:
                _allowed_val = _allowed[attribute]
            except KeyError:
                pass
            else:
                if not self._type_check(typ, _allowed_val, val, na):
                    raise NotAllowedValue(val)

        return True
Beispiel #9
0
def backchannel_logout(client, request='', request_args=None):
    """

    :param request: URL encoded logout request
    :return:
    """
    if request:
        req = BackChannelLogoutRequest().from_urlencoded(as_unicode(request))
    elif request_args:
        req = BackChannelLogoutRequest(**request_args)
    else:
        raise MissingRequiredAttribute('logout_token')

    _context = client.client_get("service_context")
    kwargs = {
        'aud':
        client.get_client_id(),
        'iss':
        _context.get('issuer'),
        'keyjar':
        _context.keyjar,
        'allowed_sign_alg':
        _context.get('registration_response').get(
            "id_token_signed_response_alg", "RS256")
    }

    logger.debug(f"(backchannel_logout) Verifying request using: {kwargs}")
    try:
        req.verify(**kwargs)
    except (MessageException, ValueError, NotForMe) as err:
        raise MessageException('Bogus logout request: {}'.format(err))
    else:
        logger.debug("Request verified OK")

    # Find the subject through 'sid' or 'sub'
    sub = req[verified_claim_name('logout_token')].get('sub')
    sid = None
    if not sub:
        sid = req[verified_claim_name('logout_token')].get('sid')

    if not sub and not sid:
        raise MessageException('Neither "sid" nor "sub"')
    elif sub:
        _state = _context.state.get_state_by_sub(sub)
    elif sid:
        _state = _context.state.get_state_by_sid(sid)
    return _state
    def post_parse_response(self, response, **kwargs):
        response = authorization.Authorization.post_parse_response(
            self, response, **kwargs)

        _idt = response.get(verified_claim_name('id_token'))
        if _idt:
            # If there is a verified ID Token then we have to do nonce
            # verification.
            _request = self.get_request_from_response(response)
            _req_nonce = _request.get('nonce')
            if _req_nonce:
                _id_token_nonce = _idt.get('nonce')
                if not _id_token_nonce:
                    raise MissingRequiredAttribute('nonce')
                elif _req_nonce != _id_token_nonce:
                    raise ValueError('Invalid nonce')
        return response
    def get_request_parameters(self, request_args=None, **kwargs):

        if request_args is None:
            request_args = {}

        try:
            _resource = request_args['resource']
        except KeyError:
            try:
                _resource = kwargs['resource']
            except KeyError:
                try:
                    _resource = self.service_context.config['resource']
                except KeyError:
                    raise MissingRequiredAttribute('resource')

        return {'url': self.query(_resource), 'method': 'GET'}
    def update_service_context(self, resp, key='', **kwargs):
        try:
            links = resp['links']
        except KeyError:
            raise MissingRequiredAttribute('links')
        else:
            for link in links:
                if link['rel'] == self.rel:
                    _href = link['href']
                    try:
                        _http_allowed = self.get_conf_attr(
                            'allow', default={})['http_links']
                    except KeyError:
                        _http_allowed = False

                    if _href.startswith('http://') and not _http_allowed:
                        raise ValueError(
                            'http link not allowed ({})'.format(_href))

                    self.service_context.set('issuer', link['href'])
                    break
        return resp
Beispiel #13
0
    def verify(self, **kwargs):
        super(IdToken, self).verify(**kwargs)

        try:
            if kwargs["iss"] != self["iss"]:
                raise IssuerMismatch("{} != {}".format(kwargs["iss"], self["iss"]))
        except KeyError:
            pass

        if "aud" in self:
            if "client_id" in kwargs:
                # check that I'm among the recipients
                if kwargs["client_id"] not in self["aud"]:
                    raise NotForMe(
                        '"{}" not in {}'.format(kwargs["client_id"], self["aud"]), self
                    )

            # Then azp has to be present and be one of the aud values
            if len(self["aud"]) > 1:
                if "azp" in self:
                    if self["azp"] not in self["aud"]:
                        raise VerificationError("Mismatch between azp and aud claims", self)
                else:
                    raise VerificationError("azp missing", self)

        if "azp" in self:
            if "client_id" in kwargs:
                if kwargs["client_id"] != self["azp"]:
                    raise NotForMe("{} != azp:{}".format(kwargs["client_id"], self["azp"]), self)

        _now = time_util.utc_time_sans_frac()

        try:
            _skew = kwargs["skew"]
        except KeyError:
            _skew = 0

        try:
            _exp = self["exp"]
        except KeyError:
            raise MissingRequiredAttribute("exp")
        else:
            if (_now - _skew) > _exp:
                raise EXPError("Invalid expiration time")

        try:
            _storage_time = kwargs["nonce_storage_time"]
        except KeyError:
            _storage_time = NONCE_STORAGE_TIME

        try:
            _iat = self["iat"]
        except KeyError:
            raise MissingRequiredAttribute("iat")
        else:
            if (_iat + _storage_time) < (_now - _skew):
                raise IATError("Issued too long ago")
            elif _iat > _now + _skew:
                raise IATError("Issued sometime in the future")

        if _exp < _iat:
            raise IATError("Expiration time can not be earlier the issued at")

        if "nonce" in kwargs and "nonce" in self:
            if kwargs["nonce"] != self["nonce"]:
                raise ValueError("Not the same nonce")

        return True
Beispiel #14
0
    def verify(self, **kwargs):
        super(IdToken, self).verify(**kwargs)

        try:
            if kwargs['iss'] != self['iss']:
                raise IssuerMismatch('{} != {}'.format(kwargs['iss'],
                                                       self['iss']))
        except KeyError:
            pass

        if "aud" in self:
            if "client_id" in kwargs:
                # check that I'm among the recipients
                if kwargs["client_id"] not in self["aud"]:
                    raise NotForMe(
                        "{} not in aud:{}".format(kwargs["client_id"],
                                                  self["aud"]), self)

            # Then azp has to be present and be one of the aud values
            if len(self["aud"]) > 1:
                if "azp" in self:
                    if self["azp"] not in self["aud"]:
                        raise VerificationError(
                            "Mismatch between azp and aud claims", self)
                else:
                    raise VerificationError("azp missing", self)

        if "azp" in self:
            if "client_id" in kwargs:
                if kwargs["client_id"] != self["azp"]:
                    raise NotForMe(
                        "{} != azp:{}".format(kwargs["client_id"],
                                              self["azp"]), self)

        _now = time_util.utc_time_sans_frac()

        try:
            _skew = kwargs['skew']
        except KeyError:
            _skew = 0

        try:
            _exp = self['exp']
        except KeyError:
            raise MissingRequiredAttribute('exp')
        else:
            if (_now - _skew) > _exp:
                raise EXPError('Invalid expiration time')

        try:
            _storage_time = kwargs['nonce_storage_time']
        except KeyError:
            _storage_time = NONCE_STORAGE_TIME

        try:
            _iat = self['iat']
        except KeyError:
            raise MissingRequiredAttribute('iat')
        else:
            if (_iat + _storage_time) < (_now - _skew):
                raise IATError('Issued too long ago')

        if 'nonce' in kwargs and 'nonce' in self:
            if kwargs['nonce'] != self['nonce']:
                raise ValueError('Not the same nonce')

        return True
Beispiel #15
0
    def verify(self, **kwargs):
        """Authorization Request parameters that are OPTIONAL in the OAuth 2.0
        specification MAY be included in the OpenID Request Object without also
        passing them as OAuth 2.0 Authorization Request parameters, with one
        exception: The scope parameter MUST always be present in OAuth 2.0
        Authorization Request parameters.
        All parameter values that are present both in the OAuth 2.0
        Authorization Request and in the OpenID Request Object MUST exactly
        match."""
        super(AuthorizationRequest, self).verify(**kwargs)

        clear_verified_claims(self)

        args = {}
        for arg in ["keyjar", "opponent_id", "sender", "alg", "encalg", "encenc"]:
            try:
                args[arg] = kwargs[arg]
            except KeyError:
                pass

        if "opponent_id" not in kwargs:
            args["opponent_id"] = self["client_id"]

        if "request" in self:
            if isinstance(self["request"], str):
                # Try to decode the JWT, checks the signature
                oidr = OpenIDRequest().from_jwt(str(self["request"]), **args)

                # check if something is change in the original message
                for key, val in oidr.items():
                    if key in self:
                        if self[key] != val:
                            # log but otherwise ignore
                            logger.warning("{} != {}".format(self[key], val))

                # remove all claims
                _keys = list(self.keys())
                for key in _keys:
                    if key not in oidr:
                        del self[key]

                self.update(oidr)

                # replace the JWT with the parsed and verified instance
                self[verified_claim_name("request")] = oidr

        if "id_token_hint" in self:
            if isinstance(self["id_token_hint"], str):
                idt = IdToken().from_jwt(str(self["id_token_hint"]), **args)
                self["verified_id_token_hint"] = idt

        if "response_type" not in self:
            raise MissingRequiredAttribute("response_type missing", self)

        _rt = self["response_type"]
        if "id_token" in _rt:
            if "nonce" not in self:
                raise MissingRequiredAttribute("Nonce missing", self)
            else:
                try:
                    if self["nonce"] != kwargs["nonce"]:
                        raise ValueError("Nonce in id_token not matching nonce in authz " "request")
                except KeyError:
                    pass

        if "openid" not in self.get("scope", []):
            raise MissingRequiredValue("openid not in scope", self)

        if "offline_access" in self.get("scope", []):
            if "prompt" not in self or "consent" not in self["prompt"]:
                raise MissingRequiredValue("consent in prompt", self)

        if "prompt" in self:
            if "none" in self["prompt"] and len(self["prompt"]) > 1:
                raise InvalidRequest("prompt none combined with other value", self)

        return True
Beispiel #16
0
def verify_id_token(msg, check_hash=False, claim="id_token", **kwargs):
    # Try to decode the JWT, checks the signature
    args = {}
    for arg in ID_TOKEN_VERIFY_ARGS:
        try:
            args[arg] = kwargs[arg]
        except KeyError:
            pass

    _jws = jws_factory(msg[claim])
    if not _jws:
        raise ValueError("{} not a signed JWT".format(claim))

    if _jws.jwt.headers["alg"] == "none":
        _signed = False
        _sign_alg = kwargs.get("sigalg")
        if _sign_alg == "none":
            _allowed = True
        else:  # There might or might not be a specified signing alg
            if kwargs.get("allow_sign_alg_none", False) is False:
                logger.info("Signing algorithm None not allowed")
                raise UnsupportedAlgorithm("Signing algorithm None not allowed")
    else:
        _signed = True
        if "allowed_sign_alg" in kwargs:
            if _jws.jwt.headers["alg"] != kwargs["allowed_sign_alg"]:
                _msg = "Wrong token signing algorithm, {} != {}".format(
                    _jws.jwt.headers["alg"], kwargs["allowed_sign_alg"]
                )
                logger.error(_msg)
                raise UnsupportedAlgorithm(_msg)

    _body = _jws.jwt.payload()
    if _signed and "keyjar" in kwargs:
        try:
            if _body["iss"] not in kwargs["keyjar"]:
                logger.info("KeyJar issuers: {}".format(kwargs["keyjar"]))
                raise ValueError('Unknown issuer: "{}"'.format(_body["iss"]))
        except KeyError:
            raise MissingRequiredAttribute("iss")

    idt = IdToken().from_jwt(str(msg[claim]), **args)
    if not idt.verify(**kwargs):
        return False

    if _signed and check_hash:
        _alg = idt.jws_header["alg"]
        hfunc = "HS" + _alg[-3:]

        if "access_token" in msg:
            if "at_hash" not in idt:
                raise MissingRequiredAttribute("Missing at_hash property", idt)
            if idt["at_hash"] != left_hash(msg["access_token"], hfunc):
                raise AtHashError("Failed to verify access_token hash", idt)

        if "code" in msg:
            if "c_hash" not in idt:
                raise MissingRequiredAttribute("Missing c_hash property", idt)
            if idt["c_hash"] != left_hash(msg["code"], hfunc):
                raise CHashError("Failed to verify code hash", idt)

    msg[verified_claim_name(claim)] = idt
    logger.info("Verified {}: {}".format(claim, idt.to_dict()))

    return True
    def to_urlencoded(self, lev=0):
        """
        Creates a string using the application/x-www-form-urlencoded format

        :return: A string of the application/x-www-form-urlencoded format
        """

        _spec = self.c_param
        if not self.lax:
            for attribute, (_, req, _ser, _, _) in _spec.items():
                if req and attribute not in self._dict:
                    raise MissingRequiredAttribute("%s" % attribute,
                                                   "%s" % self)

        params = []

        for key, val in self._dict.items():
            try:
                (_, req, _ser, _, null_allowed) = _spec[key]
            except KeyError:  # extra attribute
                try:
                    _key = key.split("#")[0]
                    (_, req, _ser, _deser, null_allowed) = _spec[_key]
                except (ValueError, KeyError):
                    try:
                        (_, req, _ser, _, null_allowed) = _spec['*']
                    except KeyError:
                        _ser = None
                        null_allowed = False

            if val is None and null_allowed is False:
                continue

            if isinstance(val, str):
                # Should I allow parameters with "" as value ???
                params.append((key, val.encode("utf-8")))
            elif isinstance(val, list):
                if _ser:
                    params.append(
                        (key, str(_ser(val, sformat="urlencoded", lev=lev))))
                else:
                    for item in val:
                        params.append((key, str(item).encode('utf-8')))
            elif isinstance(val, Message):
                try:
                    _val = json.dumps(_ser(val, sformat="dict", lev=lev + 1))
                    params.append((key, _val))
                except TypeError:
                    params.append((key, val))
            elif val is None:
                params.append((key, val))
            else:
                try:
                    params.append((key, _ser(val, lev=lev)))
                except Exception:
                    params.append((key, str(val)))

        try:
            return urlencode(params)
        except UnicodeEncodeError:
            _val = []
            for k, v in params:
                try:
                    _val.append((k, v.encode("utf-8")))
                except TypeError:
                    _val.append((k, v))
            return urlencode(_val)