Skip to content

Commit 45d35d6

Browse files
committed
Updating fedservice to adhere to draft.43 of OpenID Federation.
1 parent d01c190 commit 45d35d6

24 files changed

+179
-60
lines changed

src/fedservice/entity/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,15 @@ def __init__(self,
7373
_kwargs.update(_args)
7474
setattr(self, key, instantiate(val["class"], **_kwargs))
7575

76+
_args = {}
77+
for claim in ["trust_mark_issuers", "trust_mark_owners"]:
78+
_val = kwargs.get(claim)
79+
if _val:
80+
_args[claim] = _val
81+
7682
self.context = FederationContext(entity_id=entity_id, upstream_get=self.unit_get,
7783
authority_hints=authority_hints, keyjar=self.keyjar,
78-
preference=preference)
84+
preference=preference, **_args)
7985

8086
if client_authn_methods:
8187
self.context.client_authn_methods = client_auth_setup(client_authn_methods)
@@ -375,8 +381,7 @@ def verify_trust_mark(self, trust_mark: str, check_with_issuer: Optional[bool] =
375381
resp = self.do_request("trust_mark_status",
376382
request_args={
377383
'sub': verified_trust_mark['sub'],
378-
'id': verified_trust_mark['id'],
379-
'trust_mark_id': verified_trust_mark['id']
384+
'trust_mark_id': verified_trust_mark['trust_mark_id']
380385
},
381386
fetch_endpoint=_tmi_trust_chain.metadata["federation_entity"][
382387
"federation_trust_mark_status_endpoint"]

src/fedservice/entity/client/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def __init__(self,
4040
keyjar: Optional[KeyJar] = None,
4141
priority: Optional[List[str]] = None,
4242
trust_marks: Optional[List[str]] = None,
43+
trust_mark_issuers: Optional[dict] = None,
44+
trust_mark_owners: Optional[dict] = None,
4345
trusted_roots: Optional[dict] = None,
4446
metadata: Optional[dict] = None,
4547
):
@@ -54,6 +56,8 @@ def __init__(self,
5456
keyjar=keyjar,
5557
metadata=metadata,
5658
trust_marks=trust_marks,
59+
trust_mark_issuers=trust_mark_issuers,
60+
trust_mark_owners=trust_mark_owners,
5761
tr_priority=priority
5862
)
5963

@@ -456,7 +460,7 @@ def parse_request_response(self, service, reqresp, response_body_type="", state=
456460
elif 400 <= reqresp.status_code < 500:
457461
logger.error("Error response ({}): {}".format(reqresp.status_code, reqresp.text))
458462
# expecting an error response
459-
ctype = get_content_type(reqresp)
463+
ctype = get_content_type(reqresp)
460464
_deser_method = get_deserialization_method(ctype)
461465
if not _deser_method:
462466
_deser_method = "json"

src/fedservice/entity/context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def __init__(self,
4242
authority_hints: Optional[Union[list, str, Callable]] = None,
4343
keyjar: Optional[KeyJar] = None,
4444
preference: Optional[dict] = None,
45+
trust_mark_issuers: Optional[dict] = None,
46+
trust_mark_owners: Optional[dict] = None,
4547
**kwargs
4648
):
4749

@@ -59,6 +61,9 @@ def __init__(self,
5961
self.trust_marks = trust_marks or config.get('trust_marks', [])
6062
self.trusted_roots = trusted_roots or config.get('trusted_roots', {})
6163
self.authority_hints = authority_hints or config.get('authority_hints', [])
64+
self.trust_mark_issuers = trust_mark_issuers or config.get('trust_mark_issuers', {})
65+
self.trust_mark_owners = trust_mark_owners or config.get('trust_mark_owners', {})
66+
self.trusted_roots = trusted_roots or config.get('trusted_roots', {})
6267

6368
self.trust_chain = {}
6469
# self.issuer = self.entity_id

src/fedservice/entity/function/policy_operator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ class Add(PolicyOperator):
6565
def __call__(self, claim, metadata, metadata_policy):
6666
if claim in metadata:
6767
for val in metadata_policy[claim][self.name]:
68+
# the metadata claim value must be a list otherwise append doesn't work
69+
if isinstance(metadata, list):
70+
pass
71+
else:
72+
metadata[claim] = [metadata[claim]]
6873
if val not in metadata[claim]:
6974
metadata[claim].append(val)
7075
else:

src/fedservice/entity/function/trust_chain_collector.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ def get_metadata(self, entity_id):
181181

182182
return _ec['metadata']
183183

184+
def get_verified_self_signed_entity_configuration(self, entity_id: str) -> str:
185+
signed_entity_config = self.get_entity_configuration(entity_id)
186+
if signed_entity_config is None:
187+
return ''
188+
189+
return verify_self_signed_signature(signed_entity_config)
190+
184191
def get_federation_fetch_endpoint(self, intermediate: str) -> str:
185192
logger.debug(f'--get_federation_fetch_endpoint({intermediate})')
186193
# In cache ??

src/fedservice/entity/function/trust_mark_verifier.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ def verify_delegation(self, trust_mark, trust_anchor_id):
8989
_federation_entity = get_federation_entity(self)
9090
_collector = _federation_entity.function.trust_chain_collector
9191
# Deal with the delegation
92-
ta_fe_metadata = _collector.get_metadata(trust_anchor_id)['federation_entity']
92+
_entity_configuration = _collector.get_verified_self_signed_entity_configuration(trust_anchor_id)
9393

94-
if trust_mark['id'] not in ta_fe_metadata['trust_mark_issuers']:
94+
if trust_mark['trust_mark_id'] not in _entity_configuration['trust_mark_issuers']:
9595
return None
96-
if trust_mark['id'] not in ta_fe_metadata['trust_mark_owners']:
96+
if trust_mark['trust_mark_id'] not in _entity_configuration['trust_mark_owners']:
9797
return None
9898

9999
_delegation = factory(trust_mark['delegation'])
100-
tm_owner_info = ta_fe_metadata['trust_mark_owners'][trust_mark['id']]
100+
tm_owner_info = _entity_configuration['trust_mark_owners'][trust_mark['trust_mark_id']]
101101
_key_jar = KeyJar()
102102
_key_jar = import_jwks(_key_jar, tm_owner_info['jwks'], tm_owner_info['sub'])
103103
keys = _key_jar.get_jwt_verify_keys(_delegation.jwt)

src/fedservice/entity/server/entity_configuration.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ def process_request(self, request=None, **kwargs):
4646
else:
4747
args = {}
4848

49+
_trust_mark_issuers = _fed_entity.context.trust_mark_issuers
50+
if _trust_mark_issuers:
51+
args["trust_mark_issuers"] = _trust_mark_issuers
52+
53+
_trust_mark_owners = _fed_entity.context.trust_mark_owners
54+
if _trust_mark_owners:
55+
args["trust_mark_owners"] = _trust_mark_owners
56+
57+
4958
_ec = create_entity_statement(iss=_entity_id,
5059
sub=_entity_id,
5160
key_jar=_fed_entity.get_attribute('keyjar'),

src/fedservice/entity/server/list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def filter(self,
4242
trust_marks = conf.get('trust_marks')
4343
matched = False
4444
for trust_mark in trust_marks:
45-
if trust_mark['id'] == trust_mark_id:
45+
if trust_mark['trust_mark_id'] == trust_mark_id:
4646
matched = True
4747

4848
if matched:

src/fedservice/entity/server/who.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def process_request(self, request=None, trust_anchor: str = "" ,**kwargs):
8888
for _mark in _ec["trust_marks"]:
8989
_verified_trust_mark = _federation_entity.verify_trust_mark(
9090
_mark, check_with_issuer=True)
91-
if _verified_trust_mark.get("id") == tm_id:
91+
if _verified_trust_mark.get("trust_mark_id") == tm_id:
9292
server_to_use.append(eid)
9393
else:
9494
server_to_use = list(_srv.keys())

src/fedservice/message.py

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33

44
from cryptojwt.exception import Expired
5+
from cryptojwt.jws.jws import factory
56
from cryptojwt.jwt import utc_time_sans_frac
67
from idpyoidc import message
78
from idpyoidc.exception import MissingRequiredAttribute
@@ -93,29 +94,31 @@ def naming_constraints_deser(val, sformat="json"):
9394

9495
SINGLE_OPTIONAL_NAMING_CONSTRAINTS = (Message, False, msg_ser, naming_constraints_deser, False)
9596

97+
class InformationalMetadataExtensions(Message):
98+
c_param = {
99+
"organization_name": SINGLE_OPTIONAL_STRING,
100+
"contacts": OPTIONAL_LIST_OF_STRINGS,
101+
"logo_url": SINGLE_OPTIONAL_STRING,
102+
"policy_url": SINGLE_OPTIONAL_STRING,
103+
"homepage_uri": SINGLE_OPTIONAL_STRING,
104+
}
96105

97-
class FederationEntity(Message):
106+
class FederationEntity(InformationalMetadataExtensions):
98107
"""Class representing Federation Entity metadata."""
99-
c_param = {
100-
"federation_fetch_endpoint": SINGLE_REQUIRED_STRING,
108+
c_param = InformationalMetadataExtensions.c_param.copy()
109+
c_param.update({
110+
"federation_fetch_endpoint": SINGLE_OPTIONAL_STRING,
101111
"federation_list_endpoint": SINGLE_OPTIONAL_STRING,
102-
# "federation_registration_endpoint": SINGLE_OPTIONAL_STRING, part of OP metadata
103112
"federation_resolve_endpoint": SINGLE_OPTIONAL_STRING,
104113
"federation_trust_mark_status_endpoint": SINGLE_OPTIONAL_STRING,
105-
"federation_trust_mark_list_endpoint":SINGLE_OPTIONAL_STRING,
114+
"federation_trust_mark_list_endpoint": SINGLE_OPTIONAL_STRING,
106115
"federation_trust_mark_endpoint": SINGLE_OPTIONAL_STRING,
107-
"federation_historical_keys_endpoint":SINGLE_OPTIONAL_STRING,
116+
"federation_historical_keys_endpoint": SINGLE_OPTIONAL_STRING,
108117
"endpoint_auth_signing_alg_values_supported": SINGLE_OPTIONAL_JSON,
109-
"name": SINGLE_OPTIONAL_STRING,
110-
"contacts": OPTIONAL_LIST_OF_STRINGS,
111-
"policy_url": SINGLE_OPTIONAL_STRING,
112-
"homepage_uri": SINGLE_OPTIONAL_STRING,
113-
# "federation_trust_marks": SINGLE_OPTIONAL_JSON,
114-
"organization_name": SINGLE_OPTIONAL_STRING,
115118
# If it's a Trust Anchor
116-
"trust_mark_owners": SINGLE_OPTIONAL_DICT,
117-
"trust_mark_issuers": SINGLE_OPTIONAL_DICT,
118-
}
119+
# "trust_mark_owners": SINGLE_OPTIONAL_DICT,
120+
# "trust_mark_issuers": SINGLE_OPTIONAL_DICT,
121+
})
119122

120123

121124
def federation_entity_deser(val, sformat="json"):
@@ -255,6 +258,7 @@ class OPMetadata(ProviderConfigurationResponse):
255258
"signed_jwks_uri": SINGLE_OPTIONAL_STRING
256259
})
257260

261+
258262
class FedASConfigurationResponse(ASConfigurationResponse):
259263
c_param = ASConfigurationResponse.c_param.copy()
260264
c_param.update({
@@ -398,6 +402,52 @@ def constrains_deser(val, sformat="json"):
398402
SINGLE_OPTIONAL_CONSTRAINS = (Message, False, msg_ser, constrains_deser, False)
399403

400404

405+
class TrustMarks(Message):
406+
c_param = {}
407+
408+
def verify(self, **kwargs):
409+
for _id, spec in self.items():
410+
_trust_mark = spec.get("trust_mark")
411+
if _trust_mark:
412+
_trust_mark_id = spec.get("trust_mark_id")
413+
if _trust_mark_id:
414+
# Have to peek into the trust mark
415+
_jws = factory(_trust_mark)
416+
if not _jws:
417+
raise ValueError(f"Not a proper signed JWT: {_trust_mark}")
418+
_tm_id = _jws.jwt.payload().get("trust_mark_id")
419+
if _tm_id != _trust_mark_id:
420+
raise ValueError("The Trust Mark identifier MUST have the same value as the trust_mark_id "
421+
"claim")
422+
else:
423+
raise MissingRequiredAttribute("trust_mark_id")
424+
else:
425+
raise MissingRequiredAttribute("trust_mark")
426+
427+
428+
class TrustMarkIssuers(Message):
429+
c_param = {}
430+
431+
def verify(self, **kwargs):
432+
for owner_id, spec in self.items():
433+
if not isinstance(spec, list):
434+
raise ValueError("issuers MUST be a list")
435+
436+
437+
class TrustMarkOwners(Message):
438+
439+
def verify(self, **kwargs):
440+
# Dictionary of Trust Mark Owner information
441+
for owner_id, spec in self.items():
442+
if "sub" in spec and "jwks" in spec: # If there are other claims ignore them
443+
continue
444+
else:
445+
if "sub" not in spec:
446+
raise MissingRequiredAttribute("sub")
447+
elif "jwks" not in spec:
448+
raise MissingRequiredAttribute("jwks")
449+
450+
401451
class EntityStatement(JsonWebToken):
402452
"""The Entity Statement"""
403453
c_param = JsonWebToken.c_param.copy()
@@ -412,10 +462,15 @@ class EntityStatement(JsonWebToken):
412462
'authority_hints': OPTIONAL_LIST_OF_STRINGS,
413463
'metadata': SINGLE_OPTIONAL_METADATA,
414464
'metadata_policy': SINGLE_OPTIONAL_METADATA_POLICY,
465+
'metadata_policy_crit': OPTIONAL_LIST_OF_STRINGS,
415466
'constraints': SINGLE_OPTIONAL_CONSTRAINS,
416467
"crit": OPTIONAL_LIST_OF_STRINGS,
417468
"policy_language_crit": OPTIONAL_LIST_OF_STRINGS,
418-
'trust_marks': OPTIONAL_LIST_OF_STRINGS,
469+
"source_endpoint": SINGLE_OPTIONAL_STRING,
470+
'trust_marks': SINGLE_OPTIONAL_JSON,
471+
'trust_mark_owners': SINGLE_OPTIONAL_JSON,
472+
'trust_mark_issuers': SINGLE_OPTIONAL_JSON,
473+
#
419474
'trust_anchor_id': SINGLE_OPTIONAL_STRING
420475
})
421476

@@ -444,14 +499,24 @@ def verify(self, **kwargs):
444499
if _crit:
445500
_metadata_policy.verify(policy_language_crit=_crit, **kwargs)
446501

502+
_trust_mark_issuers = self.get("trust_mark_issuers")
503+
if _trust_mark_issuers:
504+
_tmi = TrustMarkIssuers(**_trust_mark_issuers)
505+
_tmi.verify()
506+
507+
_trust_mark_owners = self.get("trust_mark_owners")
508+
if _trust_mark_owners:
509+
_tmi = TrustMarkOwners(**_trust_mark_owners)
510+
_tmi.verify()
511+
447512

448513
class TrustMark(JsonWebToken):
449514
c_param = JsonWebToken.c_param.copy()
450515
c_param.update({
451516
"sub": SINGLE_REQUIRED_STRING,
452517
'iss': SINGLE_REQUIRED_STRING,
453518
'iat': SINGLE_REQUIRED_INT,
454-
"id": SINGLE_REQUIRED_STRING,
519+
"trust_mark_id": SINGLE_REQUIRED_STRING,
455520
"logo_uri": SINGLE_OPTIONAL_STRING,
456521
"exp": SINGLE_OPTIONAL_INT,
457522
"ref": SINGLE_OPTIONAL_STRING,
@@ -588,19 +653,22 @@ class HistoricalKeysResponse(Message):
588653
'jwks': SINGLE_REQUIRED_DICT
589654
}
590655

656+
591657
class TrustMarkRequest(Message):
592658
c_param = {
593659
"trust_mark_id": SINGLE_REQUIRED_STRING,
594660
"sub": SINGLE_REQUIRED_STRING
595661
}
596662

663+
597664
class WhoRequest(Message):
598665
c_param = {
599666
"entity_type": SINGLE_OPTIONAL_STRING,
600667
"credential_type": SINGLE_OPTIONAL_STRING,
601668
"trust_mark_id": SINGLE_OPTIONAL_STRING
602669
}
603670

671+
604672
class WhoResponse(Message):
605673
c_param = {
606674
"entities_to_use": REQUIRED_LIST_OF_STRINGS

0 commit comments

Comments
 (0)