Skip to content

Commit

Permalink
[entity.__init__] Pick a trust chain from the stored.
Browse files Browse the repository at this point in the history
[appserver.oidc.authorization] Broke out a function - entity.utils.get_keys
[openid_provider.conf.json] Added required metadata parameter
[relying_party_explicit.conf.json] Corrected the client_configs part.
[README_identity.rst] Added a section on RP with automatic registration.
[appclient.oidc.registration] Added support for metadata parameter 'client_registration_types'
[appclient.stand_alone_client_entity] Pick a trust chain from the stored.
[entity.utils] Added two new functions: 'get_keys' and 'get_verified_jwks'
  • Loading branch information
rohe committed Dec 13, 2024
1 parent 8278eeb commit 2ef091a
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 30 deletions.
51 changes: 49 additions & 2 deletions edu_federation/README_identity.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ If you now restart it it should have all the necessary information to be part of
entity_id of the trust anchor or the wallet provider you have to change the
command parameters accordingly.

OpenID Relying Party
--------------------
OpenID Relying Party - Explicit registration
--------------------------------------------

Much the same as for the openid relying party.
To start running the relying party you have to do::
Expand Down Expand Up @@ -198,6 +198,53 @@ The third would look like this::
./add_info.py -s tmp.json -t trust_anchor/subordinates


That should do it for the openid relying party.
If you now restart it it should have all the necessary information to be part of the federation.

**Note** The same goes for these commands as was noted above. If you change the
entity_id of the trust anchor or the wallet provider you have to change the
command parameters accordingly.

OpenID Relying Party - Automatic registration
---------------------------------------------

Much the same as for the openid relying party.
To start running the relying party you have to do::

./entity.py relying_party_automatic

A slightly different set of files/directories has been added

* private
Where the JWKS representation of the private federation keys are kept
* static
Where the JWKS representation of the public federation keys are kept
* trust_anchors
A directory where information about trust anchors are kept
* authority_hints
A file containing entity_ids of this entity's authority hints.
Note that there is also a authority_hints.lock file present you can safely
ignore it.
* debug.log
A log file

Now four things have to happen::

1. Adding information about trust anchors
2. Add authority hints
3. Add information about the wallet provider as a subordinate to the trust anchor

The first two are simply::

./add_info.py -s trust_anchor.json -t relying_party_automatic/trust_anchors
echo -e "https://127.0.0.1:7010" >> relying_party_automatic/authority_hints

The third would look like this::

./get_info.py -k -s https://127.0.0.1:4015 > tmp.json
./add_info.py -s tmp.json -t trust_anchor/subordinates


That should do it for the openid relying party.
If you now restart it it should have all the necessary information to be part of the federation.

Expand Down
4 changes: 4 additions & 0 deletions edu_federation/openid_provider/conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
},
"config": {
"preference": {
"client_registration_types_supported": [
"automatic",
"explicit"
],
"subject_types_supported": [
"public",
"pairwise"
Expand Down
33 changes: 27 additions & 6 deletions edu_federation/relying_party_explicit/conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@
],
"token_endpoint_auth_method": [
"private_key_jwt"
],
"client_registration_types": [
"explicit"
]
},
"services": {
Expand All @@ -156,14 +159,32 @@
"kwargs": {}
}
},
"clients": {
"client_configs": {
"": {
"redirect_uris": null,
"local": {
"issuer": "https://127.0.0.1:5000",
"redirect_uris": [
"https://{domain}:{port}/authz_cb/local"
"client_type": "oidc",
"preference": {
"client_registration_types": [
"explicit"
],
"application_type": "web",
"response_types": [
"code",
"id_token",
"code id_token"
],
"token_endpoint_auth_method": "client_secret_basic",
"scopes_supported": [
"openid"
]
},
"add_ons": {
"pkce": {
"function": "idpyoidc.client.oauth2.add_on.pkce.add_support",
"kwargs": {
"code_challenge_length": 64,
"code_challenge_method": "S256"
}
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions edu_federation/templates/rpe_opbyuid.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ <h1>Actors</h1>
<td>OpenID Provider</td>
<td>https://127.0.0.1:4020</td>
</tr>
<tr>
<td>Relying Party</td>
<td>https://127.0.0.1:4015</td>
</tr>
<tr>
<td>Relying Party</td>
<td>https://127.0.0.1:4010</td>
Expand Down
4 changes: 4 additions & 0 deletions src/fedservice/appclient/oidc/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class Registration(registration.Registration):
content_type = "application/entity-statement+jwt"
name = 'registration'

_supports = {
"client_registration_types": ["automatic", "explicit"]
}

def __init__(self, upstream_get, conf=None, client_authn_factory=None, **kwargs):
registration.Registration.__init__(self, upstream_get, conf=conf)
#
Expand Down
25 changes: 25 additions & 0 deletions src/fedservice/appclient/stand_alone_client_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ def do_provider_info(
except:
return _context.issuer

def pick_from_stored_trust_chains(self, entity_id, federation_entity):
_trust_chains = self.context.trust_chain[entity_id]
_tas = list(_trust_chains.keys())
if len(_tas) == 1:
return _trust_chains[_tas[0]]
elif federation_entity.context.tr_priority:
# Go by priority
for ta_id in federation_entity.context.tr_priority:
for ta_id in _tas:
return _trust_chains[ta_id]
return _trust_chains[_tas[0]]

def do_client_registration(
self,
request_args: Optional[dict] = None,
Expand All @@ -153,6 +165,19 @@ def do_client_registration(
_context = self.get_context()
_federation_entity = get_federation_entity(self)

# What kind of registration I can do
_ability = _context.claims.get_preference("client_registration_types")
# What the server supports
_trust_chain = self.pick_from_stored_trust_chains(_context.issuer, _federation_entity)
_supported = _trust_chain.metadata["openid_provider"]['client_registration_types_supported']
_possible = set(_ability).intersection(set(_supported))
if len(_possible) == 0:
raise ValueError("No common client registration method")

if len(_possible) == 1:
if 'automatic' in _possible:
return

if _federation_entity.get_service("registration"): # means I can do dynamic client registration
if request_args is None:
request_args = {}
Expand Down
31 changes: 11 additions & 20 deletions src/fedservice/appserver/oidc/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
from typing import List
from typing import Optional

from idpyoidc.key_import import import_jwks
from idpyoidc.message import oidc
from idpyoidc.message.oidc import RegistrationRequest
from idpyoidc.node import topmost_unit
from idpyoidc.server.oidc import authorization

from fedservice.entity.function import apply_policies
from fedservice.entity.function import get_verified_jwks
from fedservice.entity.function import get_verified_trust_chains
from fedservice.entity.function import verify_trust_chains
from fedservice.entity.utils import get_federation_entity
from fedservice.entity.utils import get_keys
from fedservice.exception import NoTrustedChains

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -72,28 +71,17 @@ def do_automatic_registration(self, client_entity_id: str, provided_trust_chain:
# If there is a jwks_uri in the metadata import keys
_root = topmost_unit(self)
if "openid_provider" in _root:
_metadata = trust_chain.metadata['openid_relying_party']
_signed_jwks_uri = _metadata.get('signed_jwks_uri')
if _signed_jwks_uri:
if _signed_jwks_uri:
_jwks = get_verified_jwks(self, _signed_jwks_uri)
if _jwks:
_keyjar = self.upstream_get('attribute', 'keyjar')
_keyjar.add(client_entity_id, _jwks)
else:
_jwks_uri = _metadata.get('jwks_uri')
if _jwks_uri:
_keyjar = self.upstream_get('attribute', 'keyjar')
_keyjar.add_url(client_entity_id, _jwks_uri)
else:
_jwks = _metadata.get('jwks')
_keyjar = self.upstream_get('attribute', 'keyjar')
_keyjar = import_jwks(_keyjar, _jwks, client_entity_id)
get_keys(
trust_chain.metadata['openid_relying_party'],
self.upstream_get('attribute', 'keyjar'),
client_entity_id,
self)

req = RegistrationRequest(**trust_chain.metadata['openid_relying_party'])
req['client_id'] = client_entity_id
kwargs = {}
kwargs['new_id'] = self.new_client_id
kwargs["set_secret"] = False

op = topmost_unit(self)['openid_provider']
_registration = op.get_endpoint("registration")
Expand All @@ -109,8 +97,10 @@ def client_authentication(self, request, auth=None, **kwargs):
_context = self.upstream_get("context")
# If this is a registered client then this should return some info
client_info = _context.cdb.get(_cid)
if client_info is None:
if client_info is None or "automatic_registered" in client_info:
if 'automatic' in _context.provider_info.get('client_registration_types_supported', []):
if client_info and "automatic_registered" in client_info: # Remove the old one
del _context.cdb[_cid]
# try the federation way
_trust_chain = request.get('trust_chain', [])
registered_client_id = self.do_automatic_registration(_cid, _trust_chain)
Expand All @@ -121,6 +111,7 @@ def client_authentication(self, request, auth=None, **kwargs):
}
else:
logger.debug('Automatic registration done')
_context.cdb[registered_client_id]["automatic_registered"] = True
if registered_client_id != _cid:
request["client_id"] = registered_client_id
kwargs["also_known_as"] = {_cid: registered_client_id}
Expand Down
14 changes: 13 additions & 1 deletion src/fedservice/entity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def get_context_attribute(self, attr, *args):
else:
return _val

def pick_trust_chain(self, trust_chains):
def pick_trust_chain(self, trust_chains: list):
"""
Pick one trust chain out of the list of possible trust chains
Expand All @@ -228,6 +228,18 @@ def pick_trust_chain(self, trust_chains):
# in the priority list. So, just pick one
return trust_chains[0]

def pick_from_stored_trust_chains(self, entity_id):
_trust_chains = self.context.trust_chain[entity_id]
_tas = list(_trust_chains.keys())
if len(_tas) == 1:
return _trust_chains[_tas[0]]
elif self.context.tr_priority:
# Go by priority
for ta_id in self.context.tr_priority:
for ta_id in _tas:
return _trust_chains[ta_id]
return _trust_chains[_tas[0]]

def get_payload(self, self_signed_statement):
_jws = as_unicode(self_signed_statement)
_jwt = factory(_jws)
Expand Down
27 changes: 26 additions & 1 deletion src/fedservice/entity/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from idpyoidc.key_import import import_jwks


def federation_entity(unit):
if hasattr(unit, "upstream_get"):
Expand All @@ -19,7 +21,7 @@ def get_federation_entity(unit):
# Look both upstream and downstream if necessary
if unit.__class__.__name__ == 'FederationEntity':
return unit
_ug = getattr(unit,'upstream_get', None)
_ug = getattr(unit, 'upstream_get', None)
if _ug:
return get_federation_entity(_ug('unit'))

Expand All @@ -32,3 +34,26 @@ def get_federation_entity(unit):
return _get_guise("federation_entity")
else:
return None


def get_verified_jwks(unit, _signed_jwks_uri):
# Fetch a signed JWT that contains a JWKS.
# Verify the signature on the JWS with a federation key
# To be implemented
return None


def get_keys(metadata, keyjar, entity_id, unit):
_signed_jwks_uri = metadata.get('signed_jwks_uri')
if _signed_jwks_uri:
if _signed_jwks_uri:
_jwks = get_verified_jwks(unit, _signed_jwks_uri)
if _jwks:
keyjar.add(entity_id, _jwks)
else:
_jwks_uri = metadata.get('jwks_uri')
if _jwks_uri:
keyjar.add_url(entity_id, _jwks_uri)
else:
_jwks = metadata.get('jwks')
_keyjar = import_jwks(keyjar, _jwks, entity_id)

0 comments on commit 2ef091a

Please sign in to comment.