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
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
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
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)
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
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
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
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
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
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)