def _parse_remote_response(self, response): """ Parse simple JWKS or signed JWKS from the HTTP response. :param response: HTTP response from the 'jwks_uri' or 'signed_jwks_uri' endpoint :return: response parsed as JSON or None """ # Check if the content type is the right one. try: if response.headers["Content-Type"] == 'application/json': logger.debug("Loaded JWKS: %s from %s" % (response.text, self.source)) try: return json.loads(response.text) except ValueError: return None elif response.headers["Content-Type"] == 'application/jwt': logger.debug("Signed JWKS: %s from %s" % (response.text, self.source)) _jws = factory(response.text) _resp = _jws.verify_compact( response.text, keys=self.verify_keys.get_signing_key()) return _resp else: logger.error('Wrong content type: {}'.format( response.headers['Content-Type'])) raise ValueError('Content-type mismatch') except KeyError: pass
def get_sub(token): _jwt = factory(token) if _jwt: return json.loads(as_unicode(_jwt.jwt.part[1]))['sub'] else: return ''
def test_sign_encrypt_id_token(self): client_info = RegistrationResponse( id_token_signed_response_alg="RS512", client_id="client_1") session_info = { "authn_req": AREQN, "sub": "sub", "authn_event": { "authn_info": "loa2", "authn_time": time.time() }, } self.endpoint_context.jwx_def["signing_alg"] = {"id_token": "RS384"} self.endpoint_context.cdb["client_1"] = client_info.to_dict() _token = self.endpoint_context.idtoken.sign_encrypt(session_info, "client_1", sign=True) assert _token _jws = jws.factory(_token) assert _jws.jwt.headers["alg"] == "RS512" client_keyjar = KeyJar() _jwks = self.endpoint_context.keyjar.export_jwks() client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) _jwt = JWT(key_jar=client_keyjar, iss="client_1") res = _jwt.unpack(_token) assert isinstance(res, dict) assert res["aud"] == ["client_1"]
def test_get_self_signed_entity_statement(): sses = entity_statement_with_x5c() collector = Collector(trust_anchors=ANCHOR, http_cli=requests.request, insecure=True) collector.ssc_dir = "." with responses.RequestsMock() as rsps: rsps.add(rsps.GET, "https://foodle.uninett.no/.well-known/openid-federation", body=sses) # Get the self-signed entity statement from a leaf self_signed_statement = collector.get_configuration_information( "https://foodle.uninett.no") _jwt = factory(self_signed_statement) assert _jwt # this should work. Not interested in the value, just that it can be done. msg = _jwt.jwt.payload() x5c_to_pems(msg["x5c"]) # Same here collector.store_ssc_cert(msg, "https://foodle.uninett.no")
def test_id_token_payload_with_access_token(self): session_id = self._create_session(AREQ) grant = self.session_manager[session_id] code = self._mint_code(grant, session_id) access_token = self._mint_access_token(grant, session_id, code) id_token = self._mint_id_token(grant, session_id, token_ref=code, access_token=access_token.value) _jws = factory(id_token.value) assert _jws.jwt.headers["alg"] == "RS256" payload = _jws.jwt.payload() assert set(payload.keys()) == { "sub", "auth_time", "aud", "exp", "iss", "iat", "nonce", "at_hash", }
def test_get_trust_mark_self_signed(self): # Verify the self signed trust marks for _jws in self.entity.server_get('context').signed_trust_marks: _jwt = factory(_jws) _payload = _jwt.jwt.payload() _tm = TrustMark(**_payload) assert _tm.verify()
def test_create_self_signed(): metadata = { "application_type": "web", "claims": ["sub", "name", "email", "picture"], "id_token_signing_alg_values_supported": ["RS256", "RS512"], "redirect_uris": ["https://foodle.uninett.no/callback"], "response_types": ["code"] } iss = "https://example.com" sub = iss key_jar = build_keyjar(KEYSPEC, issuer_id=iss) authority = ["https://ntnu.no"] _jwt = create_entity_statement(iss, sub, key_jar, metadata=metadata, authority_hints=authority) assert _jwt _verifier = factory(_jwt) keys = key_jar.get_jwt_verify_keys(_verifier.jwt) res = _verifier.verify_compact(keys=keys) assert res assert res['iss'] == iss assert res['sub'] == sub assert set(res.keys()) == { 'metadata', 'iss', 'exp', 'sub', 'iat', 'authority_hints', 'jwks' }
def test_process_request(self): session_id = self._create_session(AUTH_REQ) grant = self.session_manager[session_id] code = self._mint_code(grant, AUTH_REQ["client_id"]) _token_request = TOKEN_REQ.to_dict() _context = self.endpoint_context _token_request["code"] = code.value _req = self.token_endpoint.parse_request(_token_request, http_info={ "headers": {"dpop": DPOP_HEADER}, "url": 'https://server.example.com/token', "method": "POST" }) assert "dpop_jkt" in _req _resp = self.token_endpoint.process_request(request=_req) assert _resp["response_args"]["token_type"] == "DPoP" access_token = _resp["response_args"]["access_token"] jws = factory(access_token) _payload = jws.jwt.payload() assert "cnf" in _payload assert _payload["cnf"]["jkt"] == _req["dpop_jkt"] # Make sure DPoP also is in the session access token instance. _session_info = self.session_manager.get_session_info_by_token(access_token) _token = self.session_manager.find_token(_session_info["session_id"], access_token) assert _token.token_type == "DPoP"
def verify_self_signed_jwks(sjwt): """ Verify the signature of a signed JWT containing a JWKS. The JWT is signed by one of the keys in the JWKS. In the JWT the JWKS is stored using this format :: 'jwks': { 'keys': [ ] } :param sjwt: Signed Jason Web Token :return: Dictionary containing 'jwks' (the JWKS) and 'iss' (the issuer of the JWT) """ _jws = factory(sjwt) _json = _jws.jwt.part[1] _body = json.loads(as_unicode(_json)) iss = _body['iss'] _jwks = _body['jwks'] _kj = jwks_to_keyjar(_jwks, iss) try: _kid = _jws.jwt.headers['kid'] except KeyError: _keys = _kj.get_signing_key(owner=iss) else: _keys = _kj.get_signing_key(owner=iss, kid=_kid) _ver = _jws.verify_compact(sjwt, _keys) return {'jwks': _ver['jwks'], 'iss': iss}
def main(jwt, keys, quiet): _jw = jwe.factory(jwt) if _jw: if not quiet: print("Encrypted JSON Web Token") print('Headers: {}'.format(_jw.jwt.headers)) if keys: res = _jw.decrypt(keys=keys) json_object = json.loads(res) json_str = json.dumps(json_object, indent=2) print(highlight(json_str, JsonLexer(), TerminalFormatter())) else: print("No keys can't decrypt") sys.exit(1) else: _jw = jws.factory(jwt) if _jw: if quiet: json_object = json.loads(_jw.jwt.part[1].decode("utf-8")) json_str = json.dumps(json_object, indent=2) print(highlight(json_str, JsonLexer(), TerminalFormatter())) else: print("Signed JSON Web Token") print('Headers: {}'.format(_jw.jwt.headers)) if keys: res = _jw.verify_compact(keys=keys) print('Verified message: {}'.format(res)) else: json_object = json.loads(_jw.jwt.part[1].decode("utf-8")) json_str = json.dumps(json_object, indent=2) print('Unverified message: {}'.format( highlight(json_str, JsonLexer(), TerminalFormatter())))
def test_collect_superiors(): # entity_id = 'https://feide.no' entity_id = 'https://foodle.uninett.no' target = 'https://foodle.uninett.no' collector = DummyCollector(trusted_roots=ANCHOR, httpd=Publisher( os.path.join(BASE_PATH, 'base_data')), root_dir=os.path.join(BASE_PATH, 'base_data')) entity_statement = collector.get_entity_statement( api_endpoint='https://foodle.uninett.no/fed_api', issuer=entity_id, subject=entity_id) _config = verify_self_signed_signature(entity_statement) assert _config tree = collector.collect_superiors(_config['iss'], entity_statement) node = {entity_id: (entity_statement, tree)} chains = branch2lists(node) assert len(chains) == 1 # only one chain assert len(chains[0]) == 4 # And that chain contains 4 statements _jws00 = factory(chains[0][0]) payload = _jws00.jwt.payload() # The Federation Entity Statement will be first in line assert payload["iss"] == 'https://feide.no'
def test_sign_encrypt_id_token(): client_info = RegistrationResponse(id_token_signed_response_alg='RS512', client_id='client_1') session_info = { 'authn_req': AREQN, 'sub': 'sub', 'authn_event': { "authn_info": 'loa2', "authn_time": time.time() } } ENDPOINT_CONTEXT.jwx_def["signing_alg"] = {'id_token': 'RS384'} ENDPOINT_CONTEXT.cdb['client_1'] = client_info.to_dict() _token = sign_encrypt_id_token(ENDPOINT_CONTEXT, session_info, 'client_1', sign=True) assert _token _jws = jws.factory(_token) assert _jws.jwt.headers['alg'] == 'RS512' client_keyjar = KeyJar() _jwks = KEYJAR.export_jwks() client_keyjar.import_jwks(_jwks, ENDPOINT_CONTEXT.issuer) _jwt = JWT(key_jar=client_keyjar, iss='client_1') res = _jwt.unpack(_token) assert isinstance(res, dict) assert res['aud'] == ['client_1']
def test_id_token_payload_many_0(self): session_id = self._create_session(AREQ) grant = self.session_manager[session_id] grant.claims = {"id_token": {"given_name": None}} code = self._mint_code(grant, session_id) access_token = self._mint_access_token(grant, session_id, code) id_token = self._mint_id_token( grant, session_id, token_ref=code, code=code.value, access_token=access_token.value, ) _jwt = factory(id_token.value) payload = _jwt.jwt.payload() assert set(payload.keys()) == { "nonce", "c_hash", "at_hash", "sub", "auth_time", "given_name", "aud", "exp", "iat", "iss", }
def parse_federation_response(self, response, **kwargs): """ Takes a provider info response and parses it. If according to the info the OP has more then one federation in common with the client then the decision has to be handled higher up. For each Metadata statement that appears in the response, and was possible to parse, one :py:class:`fedservice.entity_statement.statement.Statement` instance is stored in the response by federation operator ID under the key 'fos'. :param response: A self-signed JWT containing an entity statement :returns: A list of lists of Statement instances. The innermost lists represents trust chains """ _jwt = factory(response) entity_statement = _jwt.jwt.payload() entity_id = entity_statement['iss'] _fe = self.service_context.federation_entity statement = verify_self_signed_signature(response) _tree = _fe.collect_statement_chains(entity_id, statement) _node = {entity_id: (response, _tree)} _chains = branch2lists(_node) for c in _chains: c.append(response) return [eval_chain(c, _fe.keyjar, 'openid_provider') for c in _chains]
def test_no_kid_multiple_keys_no_kid_issuer(self): a_kids = [k.kid for k in self.alice_keyjar.get_verify_key(owner="Alice", key_type="RSA")] no_kid_issuer = {"Alice": a_kids} _jwt = factory(self.sjwt_a) _jwt.jwt.headers["kid"] = "" keys = self.bob_keyjar.get_jwt_verify_keys(_jwt.jwt, no_kid_issuer=no_kid_issuer) assert len(keys) == 3
def test_no_kid_multiple_keys_no_kid_issuer_lim(self): no_kid_issuer = {'Alice': []} _jwt = factory(self.sjwt_a) _jwt.jwt.headers['kid'] = '' keys = self.bob_keyjar.get_jwt_verify_keys(_jwt.jwt, no_kid_issuer=no_kid_issuer) assert len(keys) == 3
def test_get_trust_mark_3rd_party(self): # Create the Signed JWT representing the Trust Mark _jws = self.tmi.create_trust_mark(self.id, "https://example.com") _jwt = factory(_jws) _payload = _jwt.jwt.payload() _tm = TrustMark(**_payload) assert _tm.verify()
def test_extra_headers_1(): pkey = import_private_rsa_key_from_file(full_path("./size2048.key")) payload = "Please take a moment to register today" keys = [RSAKey(priv_key=pkey)] _jws = JWS(payload, alg="RS256") sjwt = _jws.sign_compact(keys, foo="bar") _jwt = factory(sjwt) assert set(_jwt.jwt.headers.keys()) == {"alg", "foo"}
def main(fedent, entity_id, entity_type): _jws = fedent.get_configuration_information(entity_id) _jwt = factory(_jws) msg = _jwt.jwt.payload() tree = fedent.collect_statement_chains(entity_id, msg) chains = branch2lists((_jws, tree)) statements = [eval_chain(c, fedent.keyjar, entity_type) for c in chains] return statements
def test_dj_usage(): pkey = import_private_rsa_key_from_file(full_path("./size2048.key")) payload = "Please take a moment to register today" keys = [RSAKey(priv_key=pkey)] _jws = JWS(payload, alg="RS256") sjwt = _jws.sign_compact(keys) _jwt = factory(sjwt) assert _jwt.jwt.headers["alg"] == "RS256"
def test_parse_registration_response(self): # construct the entity statement the OP should return es_api = FSEntityStatementAPI(os.path.join(BASE_PATH, 'base_data'), iss="op.ntnu.no") jws = es_api.create_entity_statement("op.ntnu.no") # parse the response and collect the trust chains res = self.service['discovery'].parse_response(jws) self.service['discovery'].update_service_context(res) _sc = self.service['registration'].service_context self.service['registration'].endpoint = _sc.get('provider_info')[ 'federation_registration_endpoint'] # construct the client registration request req_args = {'entity_id': self.federation_entity.entity_id} jws = self.service['registration'].construct(request_args=req_args) assert jws # construct the information needed to send the request _info = self.service['registration'].get_request_parameters( request_body_type="jose", method="POST") # create the request _req_jwt = factory(_info['body']) payload = _req_jwt.jwt.payload() # The OP as federation entity _fe = _sc.federation_entity del _fe.keyjar["https://op.ntnu.no"] # make sure I have the private keys _fe.keyjar.import_jwks( es_api.keyjar.export_jwks(True, "https://op.ntnu.no"), "https://op.ntnu.no" ) tree = _fe.collect_statement_chains(payload['iss'], _info['body']) _node = {payload['iss']: (_info['body'], tree)} chains = branch2lists(_node) statements = [eval_chain(c, _fe.keyjar, 'openid_relying_party') for c in chains] metadata_policy = { "client_id": {"value": "aaaaaaaaa"}, "client_secret": {"value": "bbbbbbbbbb"} } # This is the registration response from the OP _jwt = _fe.create_entity_statement( 'https://op.ntnu.no', 'https://foodle.uninett.no', metadata_policy={_fe.entity_type: metadata_policy}, metadata={"federation_entity": {"trust_anchor_id": statements[0].fo}}, authority_hints=['https://feide.no']) claims = self.service['registration'].parse_response(_jwt, request_body=_info['body']) assert set(claims.keys()) == { 'id_token_signed_response_alg', 'application_type', 'client_secret', 'client_id', 'response_types', 'token_endpoint_auth_method', 'grant_types', "contacts", 'federation_type'}
def fetch_entity(fetch_endpoint, iss, sub, iss_entity_statement): _response = requests.request("GET", fetch_endpoint, verify=False, params={'iss': iss, 'sub': sub}) _jws = factory(_response.text) _key_jar = KeyJar() _key_jar.import_jwks(iss_entity_statement['jwks'], iss) _keys = _key_jar.get_jwt_verify_keys(_jws.jwt) _res = _jws.verify_compact(keys=_keys) return _res
def test_extra_headers_3(): pkey = import_private_rsa_key_from_file(full_path("./size2048.key")) payload = "Please take a moment to register today" keys = [RSAKey(priv_key=pkey)] _jws = JWS(payload, alg='RS256') _jws.set_header_claim('foo', 'bar') sjwt = _jws.sign_compact(keys, abc=123) _jwt = factory(sjwt) assert set(_jwt.jwt.headers.keys()) == {'alg', 'foo', 'abc'}
def test_signed_jwks(): _bundle = build_key_bundle(key_conf=KEYSPEC) _keys = [k.serialize() for k in _bundle.keys()] federation_key_bundle = KeyBundle(keys=_keys, federation_keys=KEY_JAR) _jws = federation_key_bundle.signed_jwks(issuer=ISSUER) _jwt = factory(_jws) assert set(_jwt.jwt.payload().keys()) == {"keys", "iat", "iss"}
def test_factory_verify_alg(): pkey = import_private_rsa_key_from_file(full_path("./size2048.key")) payload = "Please take a moment to register today" keys = [RSAKey(priv_key=pkey)] _signer = JWS(payload, alg="RS256") _signer.set_header_claim("foo", "bar") _jws = _signer.sign_compact(keys, abc=123) _verifier = factory(_jws) assert _verifier.jwt.verify_headers(alg="RS512") is False
def test_no_kid_multiple_keys(self): """This is extremely strict""" _jwt = factory(self.sjwt_a) # remove kid reference _jwt.jwt.headers["kid"] = "" keys = self.bob_keyjar.get_jwt_verify_keys(_jwt.jwt) assert len(keys) == 0 keys = self.bob_keyjar.get_jwt_verify_keys(_jwt.jwt, allow_missing_kid=True) assert len(keys) == 3
def trusted_anchor(es, key_jar): _jwt = factory(es) payload = _jwt.jwt.payload() if payload['iss'] not in key_jar: logger.warning( "Trust chain ending in a trust anchor I do not know: '%s'", payload['iss']) return False return True
def unfurl(jwt): """ Return the body of a signed JWT, without verifying the signature. :param jwt: A signed JWT :return: The body of the JWT as a 'UTF-8' string """ _rp_jwt = factory(jwt) return json.loads(_rp_jwt.jwt.part[1].decode('utf8'))
def test_well_known(self): _ctx = self.entity.context _statement = _ctx.make_configuration_statement() _jws = factory(_statement) _payload = _jws.jwt.payload() assert _payload["iss"] == _ctx.entity_id assert set(_payload.keys()) == { 'sub', 'metadata', 'authority_hints', 'jwks', 'iss', 'iat', 'exp' } assert _payload["sub"] == _ctx.entity_id
def parse_federation_registration_response(self, resp, **kwargs): """ Receives a dynamic client registration response, :param resp: An entity statement instance :return: A set of metadata claims """ _sc = self.service_context _fe = _sc.federation_entity # Can not collect trust chain. Have to verify the signed JWT with keys I have kj = self.service_context.federation_entity.keyjar _jwt = factory(resp) entity_statement = _jwt.verify_compact(resp, keys=kj.get_jwt_verify_keys( _jwt.jwt)) _trust_anchor_id = self.get_trust_anchor_id(entity_statement) chosen = None for op_statement in _fe.op_statements: if op_statement.fo == _trust_anchor_id: chosen = op_statement break if not chosen: raise ValueError('No matching federation operator') # based on the Federation ID, conclude which OP config to use op_claims = chosen.metadata # _sc.trust_path = (chosen.fo, _fe.op_paths[statement.fo][0]) _sc.provider_info = self.response_cls(**op_claims) # To create RPs metadata collect the trust chains tree = {} for ah in _fe.authority_hints: tree[ah] = _fe.collector.collect_intermediate(_fe.entity_id, ah) _node = {_fe.entity_id: (resp, tree)} chains = branch2lists(_node) # Get the policies policy_chains_tup = [ eval_policy_chain(c, _fe.keyjar, _fe.entity_type) for c in chains ] _policy = combine_policy( policy_chains_tup[0][1], entity_statement['metadata_policy'][_fe.entity_type]) logger.debug("Combined policy: {}".format(_policy)) _uev = unverified_entity_statement(kwargs["request_body"]) logger.debug("Registration request: {}".format(_uev)) _query = _uev["metadata"][_fe.entity_type] _sc.registration_response = apply_policy(_query, _policy) return _sc.registration_response