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 val_hash(self, alg): halg = "HS%s" % alg[-3:] for attr, hash_attr in self.hashable.items(): try: self[hash_attr] = left_hash(as_unicode(self[attr]), halg) except KeyError: pass else: del self[attr]
def test_update_service_context_with_idtoken(self): req_args = {'response_type': 'code', 'state': 'state', 'nonce': 'nonce'} self.service.endpoint = 'https://example.com/authorize' _info = self.service.get_request_parameters(request_args=req_args) # Build an ID Token idt = JWT(key_jar=ISS_KEY, iss=ISS, lifetime=3600) payload = {'sub': '123456789', 'aud': ['client_id'], 'nonce': 'nonce'} # have to calculate c_hash alg = 'RS256' halg = "HS%s" % alg[-3:] payload["c_hash"] = left_hash('code', halg) _idt = idt.pack(payload) resp = AuthorizationResponse(state='state', code='code', id_token=_idt) resp = self.service.parse_response(resp.to_urlencoded()) self.service.update_service_context(resp, 'state')
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 test_left_hash_hs512(): hsh = left_hash("Please take a moment to register today", "HS512") assert hsh == "_h6feWLt8zbYcOFnaBmekTzMJYEHdVTaXlDgJSWsEeY"
def test_left_hash_hs256(): hsh = left_hash("Please take a moment to register today") assert hsh == "rCFHVJuxTqRxOsn2IUzgvA"
def payload( self, session, acr="", alg="RS256", code=None, access_token=None, user_info=None, auth_time=0, lifetime=None, extra_claims=None, ): """ :param session: Session information :param acr: Default Assurance/Authentication context class reference :param alg: Which signing algorithm to use for the IdToken :param code: Access grant :param access_token: Access Token :param user_info: If user info are to be part of the IdToken :param auth_time: :param lifetime: Life time of the ID Token :param extra_claims: extra claims to be added to the ID Token :return: IDToken instance """ _args = {"sub": session["sub"]} if lifetime is None: lifetime = DEF_LIFETIME if auth_time: _args["auth_time"] = auth_time if acr: _args["acr"] = acr if user_info: try: user_info = user_info.to_dict() except AttributeError: pass # Make sure that there are no name clashes for key in [ "iss", "sub", "aud", "exp", "acr", "nonce", "auth_time" ]: try: del user_info[key] except KeyError: pass _args.update(user_info) if extra_claims is not None: _args.update(extra_claims) # Left hashes of code and/or access_token halg = "HS%s" % alg[-3:] if code: _args["c_hash"] = left_hash(code.encode("utf-8"), halg) if access_token: _args["at_hash"] = left_hash(access_token.encode("utf-8"), halg) authn_req = session["authn_req"] if authn_req: try: _args["nonce"] = authn_req["nonce"] except KeyError: pass return {"payload": _args, "lifetime": lifetime}
def id_token_payload(session, loa="2", alg="RS256", code=None, access_token=None, user_info=None, auth_time=0, lifetime=300, extra_claims=None): """ :param session: Session information :param loa: Level of Assurance/Authentication context :param alg: Which signing algorithm to use for the IdToken :param code: Access grant :param access_token: Access Token :param user_info: If user info are to be part of the IdToken :param auth_time: :param lifetime: Life time of the ID Token :param extra_claims: extra claims to be added to the ID Token :return: IDToken instance """ _args = {'sub': session['sub']} # Handle the idtoken_claims itc = id_token_claims(session) if itc.keys(): try: lifetime = itc["max_age"] except KeyError: pass for key, val in itc.items(): if key == "auth_time": _args["auth_time"] = auth_time elif key == "acr": # ["2","http://id.incommon.org/assurance/bronze"] _args["acr"] = verify_acr_level(val, loa) else: if auth_time: _args["auth_time"] = auth_time if loa: _args["acr"] = loa if user_info: try: user_info = user_info.to_dict() except AttributeError: pass # Make sure that there are no name clashes for key in ["iss", "sub", "aud", "exp", "acr", "nonce", "auth_time"]: try: del user_info[key] except KeyError: pass _args.update(user_info) if extra_claims is not None: _args.update(extra_claims) # Left hashes of code and/or access_token halg = "HS%s" % alg[-3:] if code: _args["c_hash"] = left_hash(code.encode("utf-8"), halg) if access_token: _args["at_hash"] = left_hash(access_token.encode("utf-8"), halg) authn_req = session['authn_req'] if authn_req: try: _args["nonce"] = authn_req["nonce"] except KeyError: pass return {'payload': _args, 'lifetime': lifetime}
def payload( self, session_id, alg="RS256", code=None, access_token=None, extra_claims=None, ): """ :param session_id: Session identifier :param alg: Which signing algorithm to use for the IdToken :param code: Access grant :param access_token: Access Token :param extra_claims: extra claims to be added to the ID Token :return: IDToken instance """ _context = self.server_get("endpoint_context") _mngr = _context.session_manager session_information = _mngr.get_session_info(session_id, grant=True) grant = session_information["grant"] _args = {"sub": grant.sub} if grant.authentication_event: for claim, attr in { "authn_time": "auth_time", "authn_info": "acr" }.items(): _val = grant.authentication_event.get(claim) if _val: _args[attr] = _val _claims_restriction = grant.claims.get("id_token") if _claims_restriction == {}: user_info = None else: user_info = _context.claims_interface.get_user_claims( user_id=session_information["user_id"], claims_restriction=_claims_restriction, ) if _claims_restriction and "acr" in _claims_restriction and "acr" in _args: if claims_match(_args["acr"], _claims_restriction["acr"]) is False: raise ValueError("Could not match expected 'acr'") if user_info: try: user_info = user_info.to_dict() except AttributeError: pass # Make sure that there are no name clashes for key in [ "iss", "sub", "aud", "exp", "acr", "nonce", "auth_time" ]: try: del user_info[key] except KeyError: pass _args.update(user_info) if extra_claims is not None: _args.update(extra_claims) # Left hashes of code and/or access_token halg = f"HS{alg[-3:]}" if code: _args["c_hash"] = left_hash(code.encode("utf-8"), halg) if access_token: _args["at_hash"] = left_hash(access_token.encode("utf-8"), halg) authn_req = grant.authorization_request if authn_req: try: _args["nonce"] = authn_req["nonce"] except KeyError: pass return _args