def __init__(self, entity_id: str = "", config: Optional[Union[dict, Configuration]] = None, httpc: Optional[Any] = None, cwd: Optional[str] = '', httpc_params: Optional[dict] = None): if config is None: config = {} if httpc is None: httpc = request if httpc_params is None: httpc_params = config.get("httpc_params", {}) if not entity_id: entity_id = config.get("entity_id") self.context = FederationContext(config=config, entity_id=entity_id, server_get=self.server_get) self.collector = Collector(trust_anchors=self.context.trusted_roots, http_cli=httpc, cwd=cwd, httpc_params=httpc_params) if config.get("entity_id") is None: config['entity_id'] = entity_id if 'endpoint' in config: self.endpoint = do_endpoints(config, self.server_get) else: self.endpoint = {}
def __init__(self, entity_id, trusted_roots, authority_hints=None, default_lifetime=86400, httpd=None, priority=None, entity_type='', opponent_entity_type='', registration_type='', cwd='', httpc_params=None, config=None): OidcContext.__init__(self, config, entity_id=entity_id) self.collector = Collector(trust_anchors=trusted_roots, http_cli=httpd, cwd=cwd, httpc_params=httpc_params, db_conf=self.db_conf) self.entity_id = entity_id self.entity_type = entity_type self.opponent_entity_type = opponent_entity_type for iss, jwks in trusted_roots.items(): self.keyjar.import_jwks(jwks, iss) self.authority_hints = authority_hints self.default_lifetime = default_lifetime self.tr_priority = priority or sorted(set(trusted_roots.keys())) self.registration_type = registration_type
def __init__(self, httpd=None, trusted_roots=None, root_dir='.', base_url=''): Collector.__init__(self, http_cli=httpd, trust_anchors=trusted_roots) self.root_dir = root_dir self.base_url = base_url
def __init__(self, entity_id="", trusted_roots=None, authority_hints=None, default_lifetime=86400, httpd=None, priority=None, entity_type='', opponent_entity_type='', registration_type='', cwd='', httpc_params=None, config=None): if config is None: config = {} self.entity_id = entity_id or config["entity_id"] OidcContext.__init__(self, config, entity_id=self.entity_id) self.entity_type = entity_type or config.get("entity_type") self.opponent_entity_type = opponent_entity_type or config.get( "opponent_entity_type", "") self.registration_type = registration_type or config.get( "registration_type", "") self.default_lifetime = default_lifetime or config.get( "default_lifetime", 0) self.httpc_params = httpc_params or config.get("httpc_params", {}) if not trusted_roots: trusted_roots = json.loads(open(config["trusted_roots"]).read()) self.collector = Collector(trust_anchors=trusted_roots, http_cli=httpd, cwd=cwd, httpc_params=self.httpc_params) for iss, jwks in trusted_roots.items(): self.keyjar.import_jwks(jwks, iss) if authority_hints is not None: self.authority_hints = authority_hints elif "authority_hints" in config: self.authority_hints = json.loads( open(config["authority_hints"]).read()) else: raise ConfigurationError("Missing authority_hints specification") if priority: self.tr_priority = priority elif 'priority' in config: self.tr_priority = config["priority"] else: self.tr_priority = sorted(set(trusted_roots.keys()))
def test_collect_configuration(): collector = Collector(trust_anchors=ANCHOR, http_cli=requests.request, insecure=True) with responses.RequestsMock() as rsps: rsps.add(rsps.GET, "https://foodle.uninett.no/.well-known/openid-federation", status=404) # Get the self-signed entity statement from a leaf with pytest.raises(MissingPage): collector.get_configuration_information( "https://foodle.uninett.no")
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_collect(): jwks = open( os.path.join(BASE_PATH, 'base_data', 'feide.no', 'feide.no', 'jwks.json')).read() ANCHOR = {'https://feide.no': json.loads(jwks)} KEYJAR = KeyJar() KEYJAR.import_jwks_as_json(jwks, 'https://feide.no') chain = [] _collector = Collector(trust_anchors=ANCHOR) subject = "foodle.uninett.no" with responses.RequestsMock() as rsps: _msg = open( os.path.join(BASE_PATH, 'base_data', subject, subject, 'jws')).read() rsps.add(rsps.GET, "https://foodle.uninett.no/.well-known/openid-federation", body=_msg) # Get the self-signed entity statement from a leaf _self_signed = _collector.get_configuration_information( "https://foodle.uninett.no") chain.append(_self_signed) _statement = verify_self_signed_signature(_self_signed) assert _statement authority = "" while authority not in _collector.trusted_anchors: authority = _statement['authority_hints'][0] netloc = authority[8:] with responses.RequestsMock() as rsps: _msg = open( os.path.join(BASE_PATH, 'base_data', netloc, netloc, "jws")).read() _url = "https://{}/.well-known/openid-federation".format(netloc) rsps.add(rsps.GET, _url, body=_msg) # Get the self-signed entity statement from a leaf _self_signed = _collector.get_configuration_information(authority) _statement = verify_self_signed_signature(_self_signed) assert _statement _api_endpoint = _statement['metadata']['federation_entity'][ 'federation_api_endpoint'] with responses.RequestsMock() as rsps: _msg = open( os.path.join(BASE_PATH, 'base_data', netloc, subject, "jws")).read() _url = construct_entity_statement_query( _api_endpoint, authority, "https://{}".format(subject)) rsps.add(rsps.GET, _url, body=_msg) # Get the self-signed entity statement from a leaf _signed_statement = _collector.get_entity_statement( _api_endpoint, authority, "https://{}".format(subject)) chain.append(_signed_statement) _jwt = factory(_signed_statement) _statement = _jwt.jwt.payload() subject = _statement['iss'][8:] # Now I have the chain should be 3 items in it assert len(chain) == 3 # verify the trust chain chain.reverse() verified_chain = verify_trust_chain(chain, KEYJAR) # The result is the verified statements assert len(verified_chain) == 3 # Check that the constraints are met assert meets_restrictions(verified_chain)
def test_collect_intermediate(self): _collector = self.endpoint.server_get( "endpoint_context").federation_entity.collector subject = 'https://op.ntnu.no' intermediate = 'https://ntnu.no' fedop1 = 'https://feide.no' fedop2 = 'https://swamid.se' # self-signed from subject es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(subject)) subj_sesi = es_api.create_entity_statement(get_netloc(subject)) # self-signed from intermediate es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(intermediate)) inter_sesi = es_api.create_entity_statement(get_netloc(intermediate)) # self-signed from fedop es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop1)) fedop_sesi_1 = es_api.create_entity_statement(get_netloc(fedop1)) es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop2)) fedop_sesi_2 = es_api.create_entity_statement(get_netloc(fedop2)) # intermediate on subject es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(intermediate)) inter_on_sub = es_api.create_entity_statement(get_netloc(subject)) # fedop on intermediate es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop1)) fedop_on_inter_1 = es_api.create_entity_statement( get_netloc(intermediate)) es_api = FSEntityStatementAPI(ROOT_DIR, iss=get_netloc(fedop2)) fedop_on_inter_2 = es_api.create_entity_statement( get_netloc(intermediate)) sleep(1) with responses.RequestsMock() as rsps: _url = "{}/.well-known/openid-federation".format(intermediate) rsps.add("GET", _url, body=inter_sesi, status=200) _url = "{}/.well-known/openid-federation".format(fedop1) rsps.add("GET", _url, body=fedop_sesi_1, status=200) _url = "{}/.well-known/openid-federation".format(fedop2) rsps.add("GET", _url, body=fedop_sesi_2, status=200) _url = 'https://ntnu.no/api?iss=https%3A%2F%2Fntnu.no&sub=https%3A%2F%2Fop.ntnu.no' rsps.add("GET", _url, body=inter_on_sub, status=200) _url = 'https://feide.no/api?iss=https%3A%2F%2Ffeide.no&sub=https%3A%2F%2Fntnu.no' rsps.add("GET", _url, body=fedop_on_inter_1, status=200) _url = 'https://swamid.se/api?iss=https%3A%2F%2Fswamid.se&sub=https%3A%2F%2Fntnu.no' rsps.add("GET", _url, body=fedop_on_inter_2, status=200) tree = _collector.collect_intermediate(subject, 'https://ntnu.no') assert tree assert len(_collector.config_cache) == 3 assert set(_collector.config_cache.keys()) == { 'https://ntnu.no', 'https://feide.no', 'https://swamid.se' } # The unpacked fedop1's self signed entity statement _info = _collector.config_cache['https://feide.no'] assert _info['sub'] == fedop1 assert _info['iss'] == fedop1 assert _info['metadata']['federation_entity'][ 'federation_api_endpoint'] == 'https://feide.no/api' # For each entity statement there is also the expiration time assert len(_collector.entity_statement_cache) == 6 assert set(_collector.entity_statement_cache.keys()) == { 'https://feide.no!!https://ntnu.no', 'https://feide.no!exp!https://ntnu.no', 'https://ntnu.no!!https://op.ntnu.no', 'https://ntnu.no!exp!https://op.ntnu.no', 'https://swamid.se!!https://ntnu.no', 'https://swamid.se!exp!https://ntnu.no' } # have a look at the payload _info = unverified_entity_statement( _collector. entity_statement_cache['https://swamid.se!!https://ntnu.no']) assert _info['sub'] == intermediate assert _info['iss'] == fedop2 assert _info['authority_hints'] == [fedop2] _collector_dump = _collector.dump() _c2 = Collector() _c2.load(_collector_dump) assert len(_c2.config_cache) == 3 assert set(_c2.config_cache.keys()) == { 'https://ntnu.no', 'https://feide.no', 'https://swamid.se' } # The unpacked fedop1's self signed entity statement _info = _c2.config_cache['https://feide.no'] assert _info['sub'] == fedop1 assert _info['iss'] == fedop1 assert _info['metadata']['federation_entity'][ 'federation_api_endpoint'] == 'https://feide.no/api' # For each entity statement there is also the expiration time assert len(_c2.entity_statement_cache) == 6 assert set(_c2.entity_statement_cache.keys()) == { 'https://feide.no!!https://ntnu.no', 'https://feide.no!exp!https://ntnu.no', 'https://ntnu.no!!https://op.ntnu.no', 'https://ntnu.no!exp!https://op.ntnu.no', 'https://swamid.se!!https://ntnu.no', 'https://swamid.se!exp!https://ntnu.no' } # have a look at the payload _info = unverified_entity_statement( _c2.entity_statement_cache['https://swamid.se!!https://ntnu.no']) assert _info['sub'] == intermediate assert _info['iss'] == fedop2 assert _info['authority_hints'] == [fedop2]
class FederationEntity(OidcContext): def __init__(self, entity_id, trusted_roots, authority_hints=None, default_lifetime=86400, httpd=None, priority=None, entity_type='', opponent_entity_type='', registration_type='', cwd='', httpc_params=None, config=None): OidcContext.__init__(self, config, entity_id=entity_id) self.collector = Collector(trust_anchors=trusted_roots, http_cli=httpd, cwd=cwd, httpc_params=httpc_params, db_conf=self.db_conf) self.entity_id = entity_id self.entity_type = entity_type self.opponent_entity_type = opponent_entity_type for iss, jwks in trusted_roots.items(): self.keyjar.import_jwks(jwks, iss) self.authority_hints = authority_hints self.default_lifetime = default_lifetime self.tr_priority = priority or sorted(set(trusted_roots.keys())) self.registration_type = registration_type def collect_statement_chains(self, entity_id, statement): return self.collector.collect_superiors(entity_id, statement) def eval_chains(self, chains, entity_type='', apply_policies=True): """ :param chains: A list of lists of signed JWT :param entity_type: The leafs entity type :param apply_policies: Apply metadata policies from the list on the the metadata of the leaf entity :return: List of Statement instances """ if not entity_type: entity_type = self.opponent_entity_type return [ eval_chain(c, self.keyjar, entity_type, apply_policies) for c in chains ] def create_entity_statement(self, iss, sub, key_jar=None, metadata=None, metadata_policy=None, authority_hints=None, lifetime=0, **kwargs): if not key_jar: key_jar = self.keyjar if not authority_hints: authority_hints = self.authority_hints if not lifetime: lifetime = self.default_lifetime return create_entity_statement(iss, sub, key_jar=key_jar, metadata=metadata, metadata_policy=metadata_policy, authority_hints=authority_hints, lifetime=lifetime, **kwargs) def get_configuration_information(self, subject_id): return self.collector.get_configuration_information(subject_id) def pick_metadata(self, statements): """ Pick one statement out of the list of possible statements :param statements: A list of :py:class:`fedservice.entity_statement.statement.Statement instances :return: A :py:class:`fedservice.entity_statement.statement.Statement instance """ if len(statements) == 1: # right now just pick the first: return statements[0] else: for fid in self.tr_priority: for statement in statements: if statement.fo == fid: return statement # Can only arrive here if the federations I got back and trust are not # in the priority list. So, just pick one return statements[0] def get_payload(self, self_signed_statement): _jws = as_unicode(self_signed_statement) _jwt = factory(_jws) return _jwt.jwt.payload() def collect_metadata_statements(self, self_signed_statement, metadata_type): """ :param self_signed_statement: A Self signed Entity Statement :param metadata_type: One of the metadata types defined in the specification :return: """ payload = self.get_payload(self_signed_statement) # collect trust chains _tree = self.collect_statement_chains(payload['iss'], payload) _node = {payload['iss']: (self_signed_statement, _tree)} _chains = branch2lists(_node) # verify the trust paths and apply policies return [eval_chain(c, self.keyjar, metadata_type) for c in _chains]
class FederationEntity(OidcContext): parameter = OidcContext.parameter.copy() parameter.update({ "entity_type": "", "opponent_entity_type": "", "registration_type": "", "default_lifetime": 0, "httpc_params": {}, "trusted_roots": {}, "collector": Collector, "authority_hints": [], "tr_priority": [] }) def __init__(self, entity_id="", trusted_roots=None, authority_hints=None, default_lifetime=86400, httpd=None, priority=None, entity_type='', opponent_entity_type='', registration_type='', cwd='', httpc_params=None, config=None): if config is None: config = {} self.entity_id = entity_id or config["entity_id"] OidcContext.__init__(self, config, entity_id=self.entity_id) self.entity_type = entity_type or config.get("entity_type") self.opponent_entity_type = opponent_entity_type or config.get( "opponent_entity_type", "") self.registration_type = registration_type or config.get( "registration_type", "") self.default_lifetime = default_lifetime or config.get( "default_lifetime", 0) self.httpc_params = httpc_params or config.get("httpc_params", {}) if not trusted_roots: trusted_roots = json.loads(open(config["trusted_roots"]).read()) self.collector = Collector(trust_anchors=trusted_roots, http_cli=httpd, cwd=cwd, httpc_params=self.httpc_params) for iss, jwks in trusted_roots.items(): self.keyjar.import_jwks(jwks, iss) if authority_hints is not None: self.authority_hints = authority_hints elif "authority_hints" in config: self.authority_hints = json.loads( open(config["authority_hints"]).read()) else: raise ConfigurationError("Missing authority_hints specification") if priority: self.tr_priority = priority elif 'priority' in config: self.tr_priority = config["priority"] else: self.tr_priority = sorted(set(trusted_roots.keys())) def collect_statement_chains(self, entity_id, statement): return self.collector.collect_superiors(entity_id, statement) def eval_chains(self, chains, entity_type='', apply_policies=True): """ :param chains: A list of lists of signed JWT :param entity_type: The leafs entity type :param apply_policies: Apply metadata policies from the list on the the metadata of the leaf entity :return: List of TrustChain instances """ if not entity_type: entity_type = self.opponent_entity_type return [ eval_chain(c, self.keyjar, entity_type, apply_policies) for c in chains ] def create_entity_statement(self, iss, sub, key_jar=None, metadata=None, metadata_policy=None, authority_hints=None, lifetime=0, **kwargs): if not key_jar: key_jar = self.keyjar if not authority_hints: authority_hints = self.authority_hints if not lifetime: lifetime = self.default_lifetime return create_entity_statement(iss, sub, key_jar=key_jar, metadata=metadata, metadata_policy=metadata_policy, authority_hints=authority_hints, lifetime=lifetime, **kwargs) def get_configuration_information(self, subject_id): return self.collector.get_configuration_information(subject_id) def pick_trust_chain(self, trust_chains): """ Pick one trust chain out of the list of possible trust chains :param trust_chains: A list of :py:class:`fedservice.entity_statement.statement.TrustChain instances :return: A :py:class:`fedservice.entity_statement.statement.TrustChain instance """ if len(trust_chains) == 1: # If there is only one, then use it return trust_chains[0] elif self.tr_priority: # Go by priority for fid in self.tr_priority: for trust_chain in trust_chains: if trust_chain.anchor == fid: return trust_chain # Can only arrive here if the federations I got back and trust are not # in the priority list. So, just pick one return trust_chains[0] def get_payload(self, self_signed_statement): _jws = as_unicode(self_signed_statement) _jwt = factory(_jws) return _jwt.jwt.payload() def collect_trust_chains(self, self_signed_statement, metadata_type): """ :param self_signed_statement: A Self signed Entity Statement :param metadata_type: One of the metadata types defined in the specification :return: """ payload = self.get_payload(self_signed_statement) # collect trust chains _tree = self.collect_statement_chains(payload['iss'], payload) _node = {payload['iss']: (self_signed_statement, _tree)} _chains = branch2lists(_node) logger.debug("%s chains", len(_chains)) # verify the trust paths and apply policies return [eval_chain(c, self.keyjar, metadata_type) for c in _chains]
class FederationEntity(object): name = "federation_entity" def __init__(self, entity_id: str = "", config: Optional[Union[dict, Configuration]] = None, httpc: Optional[Any] = None, cwd: Optional[str] = '', httpc_params: Optional[dict] = None): if config is None: config = {} if httpc is None: httpc = request if httpc_params is None: httpc_params = config.get("httpc_params", {}) if not entity_id: entity_id = config.get("entity_id") self.context = FederationContext(config=config, entity_id=entity_id, server_get=self.server_get) self.collector = Collector(trust_anchors=self.context.trusted_roots, http_cli=httpc, cwd=cwd, httpc_params=httpc_params) if config.get("entity_id") is None: config['entity_id'] = entity_id if 'endpoint' in config: self.endpoint = do_endpoints(config, self.server_get) else: self.endpoint = {} def collect_statement_chains(self, entity_id, statement): return self.collector.collect_superiors(entity_id, statement) def eval_chains(self, chains, entity_type='', apply_policies=True): """ :param chains: A list of lists of signed JWT :param entity_type: The leafs entity type :param apply_policies: Apply metadata policies from the list on the the metadata of the leaf entity :return: List of TrustChain instances """ _context = self.context if not entity_type: entity_type = _context.opponent_entity_type return [ eval_chain(c, _context.keyjar, entity_type, apply_policies) for c in chains ] def get_configuration_information(self, subject_id): return self.collector.get_configuration_information(subject_id) def pick_trust_chain(self, trust_chains): """ Pick one trust chain out of the list of possible trust chains :param trust_chains: A list of :py:class:`fedservice.entity_statement.statement.TrustChain instances :return: A :py:class:`fedservice.entity_statement.statement.TrustChain instance """ if len(trust_chains) == 1: # If there is only one, then use it return trust_chains[0] elif self.context.tr_priority: # Go by priority for fid in self.context.tr_priority: for trust_chain in trust_chains: if trust_chain.anchor == fid: return trust_chain # Can only arrive here if the federations I got back and trust are not # in the priority list. So, just pick one return trust_chains[0] def get_payload(self, self_signed_statement): _jws = as_unicode(self_signed_statement) _jwt = factory(_jws) return _jwt.jwt.payload() def collect_trust_chains(self, self_signed_statement, metadata_type): """ :param self_signed_statement: A Self signed Entity Statement :param metadata_type: One of the metadata types defined in the specification :return: """ payload = self.get_payload(self_signed_statement) # collect trust chains _tree = self.collect_statement_chains(payload['iss'], payload) _node = {payload['iss']: (self_signed_statement, _tree)} _chains = branch2lists(_node) logger.debug("%s chains", len(_chains)) # verify the trust paths and apply policies return [ eval_chain(c, self.context.keyjar, metadata_type) for c in _chains ] def server_get(self, what, *arg): _func = getattr(self, "get_{}".format(what), None) if _func: return _func(*arg) return None def get_context(self, *arg): return self.context def get_endpoint_context(self, *arg): return self.context def federation_endpoint_metadata(self): _config = self.context.config metadata = {} # collect endpoints endpoints = {} for key, item in self.endpoint.items(): if key in ["fetch", "list", "status", "evaluate"]: endpoints[f"federation_{key}_endpoint"] = item.full_path for attr in message.FederationEntity.c_param.keys(): if attr in _config: metadata[attr] = _config[attr] elif attr in endpoints: metadata[attr] = endpoints[attr] return {"federation_entity": metadata} def get_metadata(self): _config = self.context.config return self.federation_endpoint_metadata() def get_endpoints(self, *arg): return self.endpoint def get_endpoint(self, endpoint_name, *arg): try: return self.endpoint[endpoint_name] except KeyError: return None def get_entity(self): return self def dump(self): return { "context": self.context.dump(), "collector": self.collector.dump() } def load(self, dump): self.collector.load(dump.get("collector", {})) self.context.load(dump.get("context", {})) def get_client_id(self): return self.context.entity_id
parser = argparse.ArgumentParser() parser.add_argument('-k', "--insecure", action='store_true') parser.add_argument('-t', "--trusted_roots") parser.add_argument('-e', dest='entity_id') parser.add_argument('-c', dest='config', action='store_true') parser.add_argument('-s', dest='sub', action='store_true') parser.add_argument('-a', dest='fed_api') args = parser.parse_args() kwargs = {} if args.insecure: kwargs['insecure'] = True if args.trusted_roots: kwargs['trust_anchors'] = args.trusted_roots else: kwargs["trust_anchors"] = {} _collector = Collector(**kwargs) _info = None if args.config: _jws = _collector.get_configuration_information(args.entity_id) entity_statement = verify_self_signed_signature(_jws) json_str = json.dumps(entity_statement, indent=2) print(highlight(json_str, JsonLexer(), TerminalFormatter())) if args.sub: _info = _collector.get_entity_statement(args.fed_api, args.entity_id, args.sub)