R. Hedberg, Ed. | |
independent | |
M. Jones | |
Microsoft | |
A. Solberg | |
Uninett | |
S. Gulliksson | |
Schibsted | |
J. Bradley | |
Yubico | |
June 25, 2019 |
OpenID Connect Federation 1.0 - draft 08
openid-connect-federation-1_0
The OpenID Connect standard specifies how a Relying Party (RP) can discover metadata about an OpenID Provider (OP), and then register to obtain RP credentials. The discovery and registration process does not involve any mechanisms of dynamically establishing trust in the exchanged information, but instead rely on out-of-band trust establishment.
In an identity federation context, this is not sufficient. The participants of the federation must be able to trust information provided about other participants in the federation. OpenID Connect Federations specifies how trust can be dynamically obtained by resolving trust from a common trusted third party.
While this specification is primarily targeting OpenID Connect, it is designed to allow for re-use by other protocols and in other use cases.
This specification describes how two entities that would like to interact can dynamically fetch and resolve trust and metadata for a given protocol through the use of third-party trust issuers. A trust issuer is an entity whose main purpose is to issue statements about entities, such as OpenID Connect Relying Parties, OpenID Providers, and participating organizations. An identity federation can be realized using this specification using one or more levels of trust issuers. This specification does not mandate a specific way or restrict how a federation may be built. Instead, the specification provides the basic technical trust infrastructure building blocks needed to build a dynamic and distributed trust network such as a federation.
All entities in an OpenID Connect federation MUST have a globally unique identifier.
Note that a company, as with any real-world organization, may be represented by more than one entity in a federation.
OpenID Connect Federation trust chains rely on cryptographically signed JSON Web Token (JWT) documents, and the trust chain does not at all rely on TLS [RFC8446] in order to establish trust.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
This specification uses the terms "Claim Name", "Claim Value", "JSON Web Token (JWT)", defined by JSON Web Token (JWT) and the terms "OpenID Provider (OP)" and "Relying Party (RP)" defined by OpenID Connect Core 1.0.
This specification also defines the following terms:
An entity statement is issued by an entity and concerns a subject entity and leaf entities in a federation. An entity statement is always a signed JWT. All entities in a federation MUST be prepared to publish an entity statement about themselves.
An entity statement is composed of the following claims:
The entity statement is signed using the private key of the issuer entity, in the form of a JSON Web Signature (JWS).
The following is a non-normative example of an entity statement, before serialization and adding a signature. The example contains a critical extension jti (JWT ID) to the entity statement and one critical extension to the policy language regexp (Regular expression).
{ "iss": "https://feide.no", "sub": "https://ntnu.no", "iat": 1516239022, "exp": 1516298022, "crit": ["jti"], "jti": "7l2lncFdY6SlhNia", "policy_language_crit": [regexp"], "metadata_policy": { "openid_provider": { "issuer": {"value": "https://ntnu.no"}, "organization_name": {"value": "NTNU"}, "id_token_signing_alg_values_supported": {"subset_of": ["RS256", "RS384", "RS512"]}, "op_policy_uri": { "regexp": "^https:\/\/[\w-]+\.example\.com\/[\w-]+\.html"} }, "openid_relying_party": { "organization_name": {"value": "NTNU"}, "grant_types_supported": { "subset_of": ["authorization_code", "implicit"]}, "scopes": { "subset_of": ["openid", "profile", "email", "phone"]} } }, "jwks": { "keys": [ { "alg": "RS256", "e": "AQAB", "ext": true, "key_ops": ["verify"], "kid": "key1", "kty": "RSA", "n": "pnXBOusEANuug6ewezb9J_...", "use": "sig" } ] }, "authority_hints": { "https://edugain.org/federation": [ "https://edugain.org/federation" ] } }
In an OpenID Connect Identity Federation, entities that together build a trust chain can be categorized as:
A trust chain begins with a leaf entity's self-signed entity statement, has zero or more entity statements issued by intermediates about subordinates, and ends with an entity statement issued by the trust anchor about the top-most intermediate (if there are intermediates) or the leaf entity (if there are no intermediates) and finally a self-signed entity statement about the trust anchor.
A simple example: If we have an RP that belongs to organization A that is a member of federation F, the trust chain for such a setup will contain the following entity statements:
A trust chain MUST always be possible to order such that: If we name the entity statements ES[0] (the leaf entity's self-signed entity statement) to ES[i] (the trust anchors self-signed entity statement), i>0 then:
The signing key that MUST be used to verify ES[i] is distributed from the trust anchors to the leaf entities in some secure out-of-band's way not described in this document.
This specification does allow new metadata types to be defined, to support use cases outside OpenID Connect federations. The metadata type identifier will uniquely identify which metadata specification to utilize.
The metadata document MUST be a JSON document. Beyond that there is no restriction.
Metadata used in federations typically re-uses existing metadata standards. If needed, the metadata schema is extended with additional properties relevant in a federated context. For instance, for OpenID Connect Federations, this specification uses metadata values from OpenID Connect Discovery 1.0 and OpenID Connect Dynamic Client Registration 1.0 and adds additional values used for federations.
The metadata type identifier is openid_relying_party.
All parameters defined in Section 2 of OpenID Connect Dynamic Client Registration 1.0 are allowed in a metadata statement.
To that list is added:
The metadata type identifier is openid_provider.
All parameters defined in Section 3 of OpenID Connect Discovery 1.0 are applicable.
In addition, the following parameters are defined by this specification:
The metadata type identifier is oauth_authorization_server.
All parameters defined in Section 2 of RFC 8414 are applicable.
The metadata type identifier is oauth_client.
All parameters defined in Section 2 of RFC 7591
The metadata type identifier is oauth_resource.
The metadata type identifier is federation_entity.
Intermediates in a trust chain are of this type.
Note that the information carried here is not bound to any specific protocol but of a general nature.
The following properties are allowed:
The metadata for a specific entity can be constructed by starting with the information in leaf entity's entity statement and then applying the polices defined by the trust anchor and possible intermediates starting with the trust anchor.
Policies are expressed using a JSON object.
The following keywords represent different actions/checks that MUST be applied to the metadata.
The resulting value of the claim will be the intersection of the values specified here and the values of the claim. For instance, the claim policy:
"response_types": { "subset_of": ["code", "code token", "code id_token"]}
if applied to a metadata statement with:
"response_types": ["code", "code id_token token", "code id_token"]
will update the claim in the metadata statement to be:
"response_types": ["code", "code id_token"]
The value of the claim MUST be one of the ones listed here. As an example, if the claim policy:
"request_object_signing_alg": { "one_of": ["ES256", "ES384", "ES512"]}
is applied to the metadata statement
"request_object_signing_alg": "ES384"
the resulting claim statement will be:
"request_object_signing_alg": "ES384"
Adds the value or values specified to the list of values for the metadata statement claim. If the specified value is already present in the list, this operation has no effect. As an example, if the claim policy:
"contacts": { "add": "support@federation.example.com"}
is applied to the following claim in the metadata statement:
"contacts": "support@org.example.com"
the end result will be the claim:
"contacts": ["support@org.example.com", "support@federation.example.com"]
Disregarding what value the claim had, if any, the claims value will be set to what is specified here. As an example, if the claim policy:
"require_auth_time": { "value": true}
is applied to a metadata statement with no such claim the result will be that the metadata statement after applying the policy contains the claim:
"require_auth_time": true
If no value is assigned to this claim, then the claim's value will be set to what is specified here. As an example, if the claim policy:
"require_auth_time": { "default": true }
is applied to a metadata statement with the claim
"require_auth_time": false
then the metadata statement will afterwards contain:
"require_auth_time": false
if on the other hand the metadata statement did not contain a require_auth_time claim then the following claim statement would be added to the metadata statement:
"require_auth_time": true
If 'true' then claim MUST have a value. essential can be combined with all the other types.
"tos_uri": { "essential": true}
The upshot of applying this policy to a metadata statement is that the metadata statement MUST contain such a claim otherwise the metadata statement is incorrect.
Some policy types can be combined with others.
If there is more than one metadata policy in a trust chain, then the policies MUST be combined before they are applied to the metadata statement.
Using the notation we have previously defined metadata policies are combined starting with ES[i] and then adding the policies from ES[j] j=i-1,..,1 before applying the combined policy to the entity's metadata
These are the policy types that can be combined when combining 2 policies:
All the other policy types can NOT be combined. Which means that whatever a superior specifies are what goes.
A federations policy for RPs:
{ "scopes": { "subset_of": ["openid", "eduperson", "phone"], "default": ["openid", "eduperson"]}, "id_token_signed_response_alg": { "one_of": ["ES256", "ES384", "ES512"], "default": "ES256"}, "contacts: { "add": "helpdesk@federation.example.org"}, "application_type": { "value": "web"} }
An organization's policy for RPs:
{ "scopes": { "subset_of": ["openid", "eduperson", "address"], "default": ["openid", "eduperson"]}, "id_token_signed_response_alg": { "one_of": ["ES256", "ES384"], "default": "ES256"}, "contacts: { "add": "helpdesk@org.example.org"}, "application_type": { "one_of": ["web", "native"]} }
The combined metadata policy then becomes:
{ "scopes": { "subset_of": ["openid", "eduperson"], "default": ["openid", "eduperson"]}, "id_token_signed_response_alg": { "one_of": ["ES256", "ES384"], "default": "ES256"}, "contacts: { "add": ["helpdesk@federation.example.org"], "helpdesk@org.example.org"]}, "application_type": { "value": "web"} }
If after combining a default value for a claim policy, the result is not a subset of a subset_of policy or a one_of defined for that claim, then an error MUST be raised and the trust chain NOT used.
If applying a policy to a metadata statement results in some claims having all their values removed and it is essential that a claim has a value, then such a metadata statement MUST be regarded as broken and MUST NOT be used.
There might be parties that wants to extend the policy language defined here. If that happens then the rule is that if software compliant with this specification encounters a keyword it doesn't understand it MUST ignore it unless it is listed in a policy_language_crit list, as is done for JWS header parameters with the crit parameter. If the policy language extension keyword is listed in the policy_language_crit list and not understood, then the metadata MUST be rejected.
The following is a non-normative example of a set of policies being applied to an RP's metadata.
The RP's metadata:
{ "contacts": ["rp_admins@cs.example.com"], "redirect_uris": ["https://cs.example.com/rp1"], "response_types: ["code"] }
The federations policy for RPs:
{ "scopes": { "subset_of": ["openid", "eduperson"]}, "response_types": { "subset_of": ["code", "code id_token"]} }
The organization's policy for RPs:
{ "contacts": { "add": "helpdesk@example.com"}, "logo_uri": { "one_of": ["https://example.com/logo_small.jpg", "https://example.com/logo_big.jpg"], "default": "https://example.com/logo_small.jpg" }, "policy_uri": { "value": "https://example.com/policy.html"}, "tos_uri": { "value": "https://example.com/tos.html"} }
The metadata for the entity in question after applying the policies above, would then become:
{ "contacts": ["rp_admins@cs.example.com", "helpdesk@example.com"], "logo_uri": "https://example.com/logo_small.jpg", "policy_uri": "https://example.com/policy.html", "tos_uri": "https://example.com/tos.html" "scopes": ["openid", "eduperson"], "response_types": ["code"], "redirect_uris": ["https://cs.example.com/rp1"], }
All entities that are expected to publish entity statements about themselves or other entities, MUST expose a Federation API endpoint.
The federation API endpoint of an entity is resolved from the entity identifier. The Federation API endpoint is found using the Well-Known URIs specification, with the suffix openid-federation. The scheme, host and port is taken directly from the entity identifier combined with the following path: /.well-known/openid-federation.
If the entity identifier contains a path, it is concatenated after /.well-known/openid-federation in the same manner that path components are concatenated to the well-known identifier in the OAuth 2.0 Authorization Server Metadata [RFC8414] specification. Of course, in real multi-tenant deployments, in which the entity ID might be of the form https://multi-teanant-service.example.com/my-tenant-identifier the tenant is very likely to not have control over the path https://multi-teanant-service.example.com/.well-known/openid-federation/my-tenant-identifier whereas it is very likely to have control over the path https://multi-teanant-service.example.com/my-tenant-identifier/.well-known/openid-federation. Therefore, if using the Federation API at the URL with the tenant path after the well-known part fails, it is RECOMMENDED that callers retry at the URL with the tenant path before the well-known part (even though this violates [RFC5785]).
The Federation API is an HTTPS API that may support multiple operations. Fetching entity statements is one of the operations, and the only one that all Federation API endpoints are REQUIRED to support. All the other operations are OPTIONAL. The list of defined operations may be extended in a future.
While all operations in the specification make use of a GET request, other operations may choose to use other HTTP methods. If the operation parameter is left out, it is treated as a fetch entity statements request. Unless otherwise mentioned or agreed upon, requests to the federation API does not need to be authenticated.
Fetching entity statement is used to collect entity statements one by one in order to gather trust chains.
In order to fetch an entity statement, an entity needs to know the identifier of the entity to ask (the issuer), and the identifier of the entity that you want the statement to be about (the subject).
The request MUST be an HTTP request using the GET method and the https scheme to a resolved federation API endpoint with the following query string parameters:
The following is a non-normative example of an API request for an entity statement:
GET /.well-known/openid-federation? iss=https%3A%2F%2Fopenid.sunet.se%2Ffederation HTTP/1.1 Host: openid.sunet.se
A positive response is a signed entity statement where the content type MUST be set to application/jose. If it is negative response it will be a JSON object and the content type MUST be set to application/json. See more about error responses in Section 5.4.
The following is a non-normative example of a response:
200 OK Last-Modified: Mon, 17 Dec 2018 11:15:56 GMT Content-Type: application/jose eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJodHRwczovL3Nlc...
(the signed JWT is truncated)
An entity may use the trust negotiation operation in order to fetch resolved metadata about itself as seen/trusted by a remote peer. The result may, for instance, tell an RP what operations, scopes and claims an OP would allow the RP to use if a specific trust anchor was used.
The request MUST be an HTTP request using the GET method and the https scheme to a resolved federation API endpoint with the following query string parameters:
The following is a non-normative example of an API request for trust negotiation:
GET /.well-known/openid-federation? op=resolve_metadata& respondent=https%3A%2F%2Fopenid.sunet.se%2Ffederation& type=openid_provider& anchor=https%3A%2F%2Fswamid.se& peer=https%3A%2F%2Fidp.umu.se%2Fopenid HTTP/1.1 Host: openid.sunet.se
The response is a metadata statement that is the result of applying the metadata polices in the trust chain on the entity's metadata.
The following is a non-normative example of a response:
200 OK Last-Modified: Wed, 22 Jul 2018 19:15:56 GMT Content-Type: application/json { "organization": "University of Ume?", "contacts": ["legal@umu.se", "technical@umu.se"], "logo_uri": "https://www.umu.se/SRWStatic/img/umu-logo-left-neg-SE.svg", "policy_uri": "https://www.umu.se/en/about-the-website/legal-information/", "authorization_endpoint": "https://idp.umu.se/openid/authorization", "token_endpoint": "https://idp.umu.se/openid/token", "response_types_supported": ["code", "code id_token", "token"], "grant_types_supported": [ "authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "subject_types_supported": ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"] }
An entity may query another entity for a list of all the entities immediately subordinate to that entity that that entity is prepared to issue statements about. (In some cases, this may be a very large list.)
The request MUST be an HTTP request using the GET method and the https scheme to a resolved federation API endpoint with the following query string parameters:
The following is a non-normative example of an API request for trust negotiation:
GET /.well-known/openid-federation? op=listing& iss=https%3A%2F%2Fopenid.sunet.se%2Ffederation& type=openid_relying_party HTTP/1.1 Host: openid.sunet.se
The response MUST contain an JSON list with the known entity identifiers.
The following is a non-normative example of a response:
200 OK Last-Modified: Wed, 22 Jul 2018 19:15:56 GMT Content-Type: application/json [ "https://ntnu.andreas.labs.uninett.no/", "https://blackboard.ntnu.no/openid/callback", "https://serviceprovider.andreas.labs.uninett.no/application17" ]
If the request was malformed, or some error occurred during processing of the request, the following standardized error format should be used regardless of the operation specified.
The HTTP response code MUST be something in 400/500-range, giving an indication of the type of error. The response body MUST be a JSON object containing the claims below and the content type MUST be set to application/json.
The following is a non-normative example of an error response:
400 Bad request Last-Modified: Wed, 22 Jul 2018 19:15:56 GMT Content-Type: application/json { "operation": "fetch", "error": "invalid_request", "error_description": "Required request parameter [iss] was missing." }
An entity (e.g., the Consumer) that wants to establish trust with a remote peer, must have the remote peer's entity identifier and a list of entity IDs of trusted trust anchors together with the public version of their signing keys. The Consumer will first have to fetch sufficient entity statements to establish at least one chain of trust from the remote peer to one or more of the configured trust anchors. After that the entity MUST validate the trust chains independently, and -- if there are multiple valid trust chains and if the application demands it -- choose one.
Depending on the circumstances, the Consumer may either be handed the remote peer's self-issued entity statement, or it may have to fetch it by itself. If it needs to fetch it, it will use the process described in Section 5.1.1 with both iss and sub set to the entity ID of the remote peer.
The next step is to iterate through the list of intermediates listed in authority_hints, ignoring the authority hints that end in an unknown trust anchor, requesting an entity statement about the remote peer from each of the intermediates. If the received entity statement contains an authority hint this process is repeated. This time with the iss set to the intermediates entity ID and the sub to be the iss of the previous query. The Consumer should never attempt to fetch entity statements it already has fetched during this process (loop prevention).
Once the Consumer has found a trust anchor it wants to use it MUST complete the trust chain by fetching the trust anchor's self-signed entity statement.
A successful operation will return one or more lists of entity statements. Each of the lists terminating in a self-signed entity statement issued by a trust anchor.
If there is no path from the remote peer to at least one of the trusted trust anchors, then the list will be empty and there is no way of establishing trust in the remote peer's information. How the Consumer deals with this is out of scope for this specification.
As described in Section 2.2, a trust chain consists of an ordered list of entity statements. So whichever way the Consumer has acquired the set of entity statements, it must now verify that it is a proper trust chain using the rules laid out in that section.
To validate the chain, the following must be done:
Verifying the signature is a much more expensive operation then verifying the correctness of the statement and the timestamps. An implementer MAY therefor chose to not verify the signature until all the other checks have been done.
No information in the chain of statements should be used before the signature chain has been validated.
If multiple valid trust chains are found, the Consumer will need to decide on which one to use.
One simple rule would be to prefer a shorter chain over a longer one.
Each entity statement in a trust chain is signed and MUST have a expiration time (exp) set. The expiration time of the whole trust chain is set to the minimum value of exp within the chain.
This specification allows for a smooth process of updating metadata and public keys.
As described above in Section 6.4, each trust chain has an expiration time. A consumer of metadata using this specification MUST support refreshing a trust chain when it expires. How often a consumer SHOULD re-evaluate the trust chain depends on how quickly the consumer wants to find out that something has changed in the trust chain.
If a leaf entity publishes its public keys in the metadata part using jwks, setting an expiration time on the self-signed entity statement can be used to control how often the remote party is fetching an updated version of the public key.
If a leaf entity uses jwks_uri, the remote party will in the normal OpenID Connect way fetch the keys anew from the jwks_uri URI when it discovers that the entity uses a key it has never seen before.
A trust anchor must publish a self-signed entity statement about itself. As described above in Section 2.2, it should be at the end of the trust chain. The trust anchor SHOULD set a reasonable expiration time on that statement, such that the consumers will re-fetch the entity statement at reasonable intervals. If the trust root wants to roll over its signing keys it would have to:
It must be taken into consideration that clients may have manually configured pubic keys as part of their configuration.
Since the consumers are expected to check the trust chain at regular, reasonably frequent times, this specification does not specify a standard revocation process. Specific federations may make a different choice and will then have to add such a process.
This section describes how the trust framework in this specification is used to establish trust between an RP and an OP that has no explicit configuration or registration in advance.
There are two alternative approaches to establish trust between an RP and an OP, which we call automatic and explicit registration. Members of a federation or a community should agree upon which one to use. While implementations should support both methods, deployments may choose to disable the use of one of them.
The trust between the entities is established using the above described extensions in the first two steps of the communication between an RP and an OP. How the RP found the OP in the first place is out of scope for this document.
------ ------ | | <--- 1) Discovery ------------------> | | | RP | ---- 2) Authentication request -----> | OP | | | | | ------ ------
The client_id of the RP MUST be set identically to the RP entity identifier.
Without a registration process, the RP does not have a client_secret. Instead the automatic registration model requires the RP to make use of asymmetric cryptography.
The RP MUST host a Federation API that allows the OP to fetch the entity statements.
The authentication request is as specified in OpenID Connect Core.
The RP MUST authenticate at the authentication endpoint using the private_key_jwt method described in the client authentication section of OpenID Connect Core 1.0.
An authorization request example:
GET /authorization? redirect_uri=https%3A%2F%2Fexample.com%2Fauthz_cb &scope=openid+profile+email+address+phone &response_type=code &nonce=4LX0mFMxdBjkGmtx7a8WIOnB&state=JpRTpu9eGXiP4thsK ... &state=YmX8PM9I7WbNoMnnieKKBiptVW0sP2OZ &client_id=https%3A%2F%2Flocalhost%3A8090%2Firp &client_assertion=eyJhbGciOiJSUzI1NiIs ... qx7xHcvPOdIhnpg &client_assertion_type= urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer HTTP/1.1 Host: https://example.org
When the OP receives an incoming authentication request, the OP supports OpenID Connect Federation and the incoming client_id is a valid URL, the OP should try to resolve and fetch trust chains starting with the RP's entity statement as described in Section 6.1.
The OP should validate the possible trust chains, as described in Section 6.2, and resolve the RP metadata with type openid_relying_party.
The OP should consider the resolved metadata of the RP, and verify that it complies with the client metadata specification in OpenID Connect Dynamic Client Registration 1.0.
Once the OP has the RP's metadata, it can verify the client authentication.
If the OP fails to establish trust with the RP, it should use the invalid_request error code, and an error_description that aids the RP to understand what is wrong.
This method involves performing an explicit registration of a new client the first time a RP interacts with an OP using something that basically follows the steps in OpenID Connect Dynamic Client Registration 1.0 but where the client registration request is a signed entity statement.
The RP will start by gathering the OP's metadata using the process specified in Section 6 above.
The OP MUST support OpenID Dynamic Client Registration as extended by this specification. This is signaled by having the claim federation_registration_endpoint in the OP's metadata.
Given that the OP supports explicit registration, the RP progresses as follows:
A client registration using this specification is not expected to be valid forever. The entity statements exchanged all have expiration times, which means that the registration will eventually time out. An OP can also for administrative reasons decide that a client registration is not valid anymore. An example of this could be that the OP leaves the federation in use.
At regular intervals the RP MUST:
What is regarded as reasonable intervals will depend on federation policies and risk assessment by the maintainer of the RP.
At regular intervals the OP MUST:
An OP MUST NOT assign an expiration time to a RP's registration that is later then the trust chains expiration time.
TBD Register federation_types_supported for OP metadata with initial values automatic, explicit.
TBD Register federation_type for RP registration metadata.
TBD Register federation_registration_endpoint for the OP metadata.
TBD
[RFC8446] | Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018. |
A service Foodle would like to offer its services to all OPs in eduGAIN. Foodle is managed and registered by the university NTNU. NTNU is part of the Norwegian Feide federation. Foodle is also directly trusted in the Swedish SWAMID federation. Both Feide and SWAMID are part of the international eduGAIN federation.
The Foodle service chooses to use the entity identifier https://foodl.org/. And upon deployment, Foodle is setup with an RSA key pair, with the following public key:
{ "kid": "key1", "use": "sig", "kty": "RSA", "alg": "RS256", "n": "pnXBOusEANuug6ewezb9J_XbxbSGEISyA75wBGkerPNg6WTXmmxJ-DV1U4sCu RqhSdo3Uncmw6-01bZKCtAyRHT_TOZN2TMfNPRsfLkOstVofyfxg5oIWViLX9IDG_iZVd q6_T6yOuufOIvqpaeBMwSKuDXHNa_DU0aUu_3kOAc5_2hD4Dq-XXtum-oix2EPkNSbFfP qFIp5n4gS1XrzGzuNQiDw82k-H6mWN0wlVWfqLxJA9DZikAX7x9feipn36wxDH-XUlzDD Ui3nfnC8GSkT-CYII3oZPsIgMV527iQGVsehIV9KqTF2FnaP83cqV9YgvMfhs1wrx4L3Z -3B8Q", "e": "AQAB", "key_ops": ["verify"], "ext": true }
Foodle offers a WebFinger interface and a metadata API according to this specification, with the ability to issue entity statements about itself.
How trust is established and how entities become part of a federation is out of scope of this specification. It could involve some kind of non-technical contract, agreement or term of use that is established, followed by a federation or trust issuer that registers an entity identifier, public key and a set of metadata that restricts the delegated trust that is represented in the entity statement about the joining party.
The following example, assumes the following trust relations are established, and the following entities are able to issue entity statements:
Foodle has a local trust root configuration that contains public signing keys for known federations:
"https://www.sunet.se/swamid"
{ "keys": [ { "kty": "RSA", "alg": "RS256", "n": "v6xydqciFKGfvQaqYGmk9A7etbfvNY...", "e": "AQAB", "key_ops": ["verify"], "ext": true, "kid": "9Gx7-Kkz_18DhpQ...", "use": "sig" } ] }
Let us assume a student from Umeå would like to login at Foodle. Some sort of discovery process involves the end user choosing an OP. OpenID Discovery using the e-mail address is one option. Foodle presenting a list of available OPs for the user to choose from is another.
After the discovery process, Foodle knows that the user would like to login using the OP with entity identifier https://www.umu.se/openid.
Foodle performs a request to fetch the self-issued entity statement using the Federation API of the OP.
GET /.well-known/openid-federation/openid? iss=https%3A%2F%2Fumu.se%2Fopenid HTTP/1.1 Host: www.umu.se
Yielding this response:
HTTP/1.1 200 OK Content-Type: application/json "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InVtdSJ9.eyJpYXQi..."
The API endpoint returns a signed entity statement. In this case we looked for a self-issued statement from the Umeå university. We then decode and inspect the content:
{ "iat": 1539174048, "exp": 1539177648, "iss": "https://umu.se/openid", "sub": "https://umu.se/openid", "metadata": { "openid_provider": { "federation_types_supported": ["automatic", "explicit"], "authorization_endpoint": "https://idp.umu.se/openid/authorization", "token_endpoint": "https://idp.umu.se/openid/token", "response_types_supported": ["code", "code id_token", "token"], "grant_types_supported": [ "authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "subject_types_supported": ["pairwise", "public"], "id_token_signing_alg_values_supported": ["RS256"], "logo_uri": "https://www.umu.se/img/umu-logo-left-neg-SE.svg", "policy_uri": "https://www.umu.se/en/website/legal-information/" } }, "authority_hints": { "https://www.sunet.se/swamid": ["https://edugain.org/oidc"], "https://kalmar2.org/openid": [] }, "jwks": { "keys": [ { "kty": "RSA", "alg": "RS256", "n": "z1V1kyi6qwmXfKsfhVqKUMmQH3AixN...", "e": "AQAB", "key_ops": ["verify"], "ext": true, "kid": "8S9-dy4GN8_-z...", "use": "sig" } ] } }
In order to establish trust with this OP, the Foodle RP would need to fetch sufficient entity statements to represent a complete chain from the self-issued statement to the locally configured trust root, which contains SWAMID.
The information found in the authority_hints is critical in order to dynamically discover the trust chain. If such hints are not present, the RP may fall back to fixed configured trust roots to ask for entity statements.
In this example, Foodle now fetches an entity statement from SWAMID using the Federation API endpoint of SWAMID, discovered in the authority_hints claim.
GET /.well-known/openid-federation? iss=https%3A%2F%2Fwww.sunet.se%2Fswamid& sub=https%3A%2F%2Fumu.se%2Fopenid HTTP/1.1 Host: www.sunet.se
Yielding this response:
HTTP/1.1 200 OK Content-Type: application/json "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZlaWRlIn0.eyJpY..."
The decoded version of the entity statement is:
{ "iat": 1539174048, "exp": 1539177648, "metadata_policy": { "openid_provider": { "subject_types_supported": { "value": ["pairwise"]}, "id_token_signing_alg_values_supported":{ "subset_of": ["RS256", "RS512"], "default": ["RS256", "RS512"] } "organization": { "value": "University of Umeå"}, "contacts": { "add": ["legal@umu.se", "technical@umu.se"]} }, "openid_relying_party": {} }, "iss": "https://www.sunet.se/swamid", "sub": "https://umu.se/openid", "jwks": { "keys": [ { "kty": "RSA", "alg": "RS256", "n": "v6xydqciFKGfvQaqYGmk9A7etbfvNY...", "e": "AQAB", "key_ops": ["verify"], "ext": true, "kid": "9Gx7-Kkz_18DhpQ...", "use": "sig" } ] }, "authority_hints": { "https://www.sunet.se/swamid": ["https://www.sunet.se/swamid"] } }
Notice that the entity statement about University of Umeå also contains an entry for openid_relying_party metadata. This metadata policy indicates that SWAMID expresses this university to be trusted to issue its own OpenID Relying Parties and OpenID Providers without the need for registering these directly in SWAMID.
The last step then is that Foodle now fetches an entity statement from SWAMID about SWAMID using the Federation API endpoint of SWAMID, discovered in the authority_hints claim.
GET /.well-known/openid-federation? iss=https%3A%2F%2Fwww.sunet.se%2Fswamid& sub=https%3A%2F%2Fwww.sunet.se%2Fswamid HTTP/1.1 Host: www.sunet.se
Yielding this response:
HTTP/1.1 200 OK Content-Type: application/json "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZlaWRlIn0.eyJpY..."
The decoded version of the entity statement is:
{ "iat": 1539174050, "exp": 1539177650, "metadata": { "federation_entity": { "name": "SWAMID", "homepage_uri": "https://swamid.se/index.html" }, }, "iss": "https://www.sunet.se/swamid", "sub": "https://www.sunet.se/swamid", "jwks": { "keys": [ { "e": "AQAB", "kid": "SnJJSVFodkFXOX...", "kty": "RSA", "n": "5uGR_-KKce9ycV6...", "use": "sig" } ] } }
These three entity statements are sufficient to establish a path from the locally configured trust anchor which trust SWAMID, to the self-issued statement from the University of Umeå. Here are the steps performed to validate the trust chain:
The output from the trust chain validation is an ordered list of entity statements. In order to extract the needed metadata, we need to look at the metadata type relevant in the given context. In this case, we are establishing trust with an OP, and we take the openid_provider metadata object from the entity statement published by the OP and the policy statements from the other entities in the trust chain:
SWAMID's metadata policy for an openid_provider:
{}
UMU's metadata policy for an openid_provider:
{ "subject_types_supported": {"value": ["pairwise"]}, "id_token_signing_alg_values_supported": { "subset_of": ["RS256", "RS512"], "default": ["RS256", "RS512"] } "organization": {"value": "University of Umeå"}, "contacts": {"add": ["legal@umu.se", "technical@umu.se"]} }
and finally
The OP's metadata statement:
{ "authorization_endpoint": "https://idp.umu.se/openid/authorization", "token_endpoint": "https://idp.umu.se/openid/token", "response_types_supported": ["code", "code id_token", "token"], "grant_types_supported": [ "authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "subject_types_supported": ["pairwise", "public"], "id_token_signing_alg_values_supported": ["RS256"], "logo_uri": "https://www.umu.se/SRWStatic/img/umu-logo-left-neg-SE.svg", "policy_uri": "https://www.umu.se/en/about-the-website/legal-information/" }
Applying the metadata policies to the metadata produces the following result:
{ "organization": "University of Umeå", "contacts": ["legal@umu.se", "technical@umu.se"], "logo_uri": "https://www.umu.se/SRWStatic/img/umu-logo-left-neg-SE.svg", "policy_uri": "https://www.umu.se/en/about-the-website/legal-information/", "authorization_endpoint": "https://idp.umu.se/openid/authorization", "token_endpoint": "https://idp.umu.se/openid/token", "response_types_supported": ["code", "code id_token", "token"], "grant_types_supported": [ "authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "subject_types_supported": ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"] }
Foodle after establishing trust with the University of Umeå and extracted metadata and a set of metadata policies, will send an authentication request to the OP. This example uses automatic registration.
Here is an example of an authentication request:
GET /authorize? response_type=code &scope=openid%20profile%20email &client_id=https%3A%2F%2Ffoodl.org%2F &state=2ff7e589-3848-46da-a3d2-949e1235e671 &redirect_uri=https%3A%2F%2Ffoodl.org%2Fopenid%2Fcallback &client_assertion=eyJhbGciOiJSUzI1NiIs ... qx7xHcvPOdIhnpg &client_assertion_type= urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer HTTP/1.1 Host: idp.umu.se
The OP receiving this authentication request will, unless the RP is cached or statically configured, start to dynamically fetch and establish trust with the RP.
The OP needs to establish a trust chain for the RP from which an authentication was received. The OP in this example are configured with public key of 2 federations:
"https://edugain.org/oidc"
{ "keys": [ { "kty": "RSA", "use": "sig", "alg": "RS256", "n": "qnd5_krrHKzuJzb5_YEt4sP-YOGSbf...", "e": "AQAB", "key_ops": ["verify"], "ext": true, "kid": "SX7_-Q0heLZq6T..." } ] }
and
"https://www.sunet.se/swamid"
{ "keys": [ { "kty": "RSA", "alg": "RS256", "n": "v6xydqciFKGfvQaqYGmk9A7etbfvNY...", "e": "AQAB", "key_ops": ["verify"], "ext": true, "kid": "9Gx7-Kkz_18DhpQ...", "use": "sig" } ] }
The RP starts to resolve metadata for the client identifier https://foodl.org/ by fetching the self-issued entity statement using the Federation API, as described in Section 6.1.
In this case, there are two possible trust chains:
A research project has pooled resources and bought an extremely rare and expensive equipment (EREE) that MUST be accessible by all project participants disregarding which university/research organization/company they belong for. To that end, the research project has created its own federation (EREE) and is expecting the participants to get their organization's OPs to register with the EREE federation. These OPs are, of course, expected to be members in one or more other federations. Therefore, we have to an EREE service and an EREE federation. Since the EREE equipment is located in Sweden, the EREE service is also member of the SWAMID federation.
The EREE service choose to use the entity identifier https://srv.eree.example.org/. And upon deployment, EREE is setup with an elliptic curve key pair, with the following public key:
{ "keys": [ { "kty": "EC", "use": "sig", "kid": "bmRkVmk0QUY3UUdnM3NDekI4VGptRUIxVk5lRXIyVE9rRUZpMUpNbGJ...", "crv": "P-256", "x": "ypFDCBLLT7lRP8UPo12ycnIkyFjeL1yco_Iu7VZoeDk", "y": "1sO4UIY1Iil0_PYobPKhuhs5ocQqVWYCujXcfo47epg" } ] }
The EREE service is provided files containing authority_hints by its superiors. From the EREE federation it gets:
{"https://eree.example.org":["https://eree.example.org"]}
from SWAMID:
{"https://swamid.se":["https://swamid.se"]}
and from UNINETT:
{"https://uninett.no":["https://uninett.no"]}
and so on...
On the federations side:
And finally, from the federations the EREE service also receives the public part of the federations signing keys.
A researcher from Umeå wants to access the EREE service. The EREE service provides a discovery service which allows the researcher to choose which OP to use. In this case, https://op.umu.se/.
Using the entity ID (issuer ID) of the OP the service performs a fetch entity statement request as described in Section 5.1.1.
GET /.well-known/openid-federation?iss=https%3A%2F%2Fop.umu.se HTTP/1.1 Host: op.umu.se
HTTP/1.1 200 OK Content-Type: application/json eyJhbGciOiJFUzI1NiIsImtpZCI6IlFVOUxUbkpzTjJ4VVRYQkZSM040T1Z...
The decoded version of the entity statement is:
{ "authority_hints": { "https://eree.example.org": [ "https://eree.example.org" ], "https://swamid.se": [ "https://swamid.se" ] }, "exp": 1543851936, "iat": 1543247136, "iss": "https://op.umu.se", "sub": "https://op.umu.se", "jwks": { "keys": [ { "crv": "P-256", "kid": "QU9LTnJsN2xUTXBFR3N4OVZOeTlyejFrWWthYWlaTllYMDR...", "kty": "EC", "use": "sig", "x": "DU6e1SjvW3Gqcd7up-n8s1N6Zlm2cNlZjYqL3O36v1A", "y": "pEtk0_fSKN56V-2hDnzFUbaw8-v0QBjNoT2KaZ7pqIc" } ] }, "metadata": { "openid_provider": { "federation_types_supported": ["explicit"], "authorization_endpoint": "https://op.umu.se/authorization", "federation_registration_endpoint": "https://op.umu.se/fedreg", "grant_types_supported": [ "authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "id_token_signing_alg_values_supported": [ "RS256" ], "logo_uri": "https://www.umu.se/img/umu-logo-left-neg-SE.svg", "policy_uri": "https://www.umu.se/en/website/legal-information/", "response_types_supported": [ "code", "code id_token", "token" ], "subject_types_supported": [ "pairwise", "public" ], "token_endpoint": "https://op.umu.se/token", "userinfo_endpoint": "https://op.umu.se/user" } } }
In order to establish trust with this OP, the EREE service provider would need to fetch sufficient entity statements to represent a complete chain from the self-issued statement to the trust anchor that represents the EREE federations.
The authority_hints in the self-signed entity statement points to 2 trust anchors "https://eree.example.org" and "https://swamid.se" of these only the EREE one is interesting. The RP therefore chooses to only follow that trust path. The next step being to fetch an entity statement about "https://op.umu.se" signed by the EREE federation. This is done by doing a fetch entity statement:
GET /.well-known/openid-federation? iss=https%3A%2F%2Feree.example.org& sub=https%3A%2F%2Fop.umu.se HTTP/1.1 Host: eree.example.org
HTTP/1.1 200 OK Content-Type: application/json eyJhbGciOiJFUzI1NiIsImtpZCI6IlFuRlJWMEZ6YjE5NVdW...
The decoded version of the returned entity statement is:
{ "exp": 1543852816, "iat": 1543248016, "iss": "https://eree.example.org/", "jwks": { "keys": [ { "crv": "P-256", "kid": "QU9LTnJsN2xUTXBFR3N4OVZOeTlyejFrWWthYWlaTllYMDRXSk", "kty": "EC", "use": "sig", "x": "DU6e1SjvW3Gqcd7up-n8s1N6Zlm2cNlZjYqL3O36v1A", "y": "pEtk0_fSKN56V-2hDnzFUbaw8-v0QBjNoT2KaZ7pqIc" } ] }, "metadata_policy": { "openid_provider": {} }, "sub": "https://op.umu.se", "authority_hints": { "https://eree.example.org": [ "https://eree.example.org" ] } }
A thing worth noting about the response:
The final step is to get the federation's view of itself:
GET /.well-known/openid-federation? iss=https%3A%2F%2Feree.example.org& sub=https%3A%2F%2Feree.example.org HTTP/1.1 Host: eree.example.org
With the response:
HTTP/1.1 200 OK Content-Type: application/json eyJhbGciOiJFUzI1NiIsImtpZCI6IlFuRlJWMEZ6YjE5NVdW...
The decoded version of the returned entity statement is:
{ "exp": 1543852820, "iat": 1543248020, "iss": "https://eree.example.org/", "sub": "https://eree.example.org/", "jwks": { "keys": [ { "crv": "P-256", "kid": "QnRSZUlvUWJ6UHdoU0V0VmFoOWs0d...", "kty": "EC", "x": "Dy8Va4JFw419muNj1gEYb3cF9xNonrY1PfpEEyqKH6A", "y": "lbLVj3opCTspgrySmBVvSPwgxRTCAJUfqZMcN3b0Wvk" "use": "sig", } ] }, "metadata": { "federation_entity": { "name": "Extremely rare and expensive equipment", "homepage_uri": "https://www.eree.org/index.html" }, } }
A thing worth noting about the response:
These three entity statements are sufficient to establish a path from the locally configured trust anchor that trusts the EREE federation, to the self-issued statement from the OP at the University of Umeå. Here are the steps performed to validate the trust chain, as described in Section 6.2. We start with the signed entity statement issued by EREE about itself.
Now we can work on the self-signed entity statement published by the OP at UmU.
The output from the trust chain validation is an ordered list of entity statements. In order to extract the needed metadata, we need to look at the metadata type relevant in the given context. In this case, we are establishing trust with an OP, and we use the openid_provider metadata and metadata_policy objects of the trust chain.
Metadata polices:
{}
Metadata statement:
{ "authorization_endpoint": "https://op.umu.se/authorization", "federation_registration_endpoint": "https://op.umu.se/fedreg", "grant_types_supported": [ "authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "id_token_signing_alg_values_supported": [ "RS256" ], "logo_uri": "https://www.umu.se/img/umu-logo-left-neg-SE.svg", "policy_uri": "https://www.umu.se/en/website/legal-information/", "response_types_supported": [ "code", "code id_token", "token" ], "subject_types_supported": [ "pairwise", "public" ], "token_endpoint": "https://op.umu.se/token", "userinfo_endpoint": "https://op.umu.se/user" }
Since there is no metadata policy defined earlier in the trust chain, the response will just be the metadata statement as it is.
Now when the RP has trusted information about the OP it can do a dynamic client registration. To that end it collects information about itself that it wants to register. This should be no different from what a normal OpenID Connect RP using dynamic client registration does. To this it adds "federation_type"="explicit", the RP federation signing keys, the sub and authority_hints. Ones it has all that information it creates an entity statement. The result of all this work may look something like this:
{ "authority_hints": { "https://eree.example.org": [ "https://eree.example.org" ] }, "jwks": { "keys": [ { "crv": "P-256", "kid": "bmRkVmk0QUY3UUdnM3NDekI4VGptRUIxVk5lRXIyVE9rRUZpMUpNb...", "kty": "EC", "use": "sig", "x": "ypFDCBLLT7lRP8UPo12ycnIkyFjeL1yco_Iu7VZoeDk", "y": "1sO4UIY1Iil0_PYobPKhuhs5ocQqVWYCujXcfo47epg" } ] }, "metadata": { "openid_relying_party": { "federation_type": "explicit", "application_type": "web", "request_object_signing_alg": "ES256", "response_types": [ "code" ], "scope": [ "openid", "email" ], "token_endpoint_auth_method": "private_key_jwt", "token_endpoint_auth_signing_alg": "ES256", "userinfo_signed_response_alg": "ES256" } }, "iss": "https://rp.eree.example.org", "sub": "https://rp.eree.example.org" }
Next, it self-signs this statement and sends it as a client registration request to the federation_registration_endpoint of the OP.
To collect the trust chains, the OP uses the authority_hints in the self-signed entity statement it received from the RP (the client registration request).
In this case, there is only one, which points to https://eree.example.org. Therefore, the OP fetches the entity statement that the EREE federation publishes on the EREE RP.
GET /.well-known/openid-federation? iss=https%3A%2F%2Feree.example.org& sub=https%3A%2F%2Frp.eree.example.org HTTP/1.1 Host: eree.example.org
With the response:
HTTP/1.1 200 OK Content-Type: application/json eyJhbGciOiJFUzI1NiIsImtpZCI6IlFuRlJWMEZ6YjE5NVd...
Unpacked this becomes:
{ "exp": 1543865440, "iat": 1543260640, "iss": "https://eree.example.org", "jwks": { "keys": [ { "crv": "P-256", "kid": "bmRkVmk0QUY3UUdnM3NDekI4VGptRUIxVk5lRXIyVE9rRUZpM...", "kty": "EC", "use": "sig", "x": "ypFDCBLLT7lRP8UPo12ycnIkyFjeL1yco_Iu7VZoeDk", "y": "1sO4UIY1Iil0_PYobPKhuhs5ocQqVWYCujXcfo47epg" } ] }, "metadata_policy": { "openid_relying_party": { "federation_type": "explicit", "application_type": {"value": "web"}, "request_object_signing_alg": {"value":"ES256"}, "response_types": {"subset_of":["code"]}, "scope": {"subset_of":["openid","email"]}, "token_endpoint_auth_method": {"value": "private_key_jwt"}, "token_endpoint_auth_signing_alg": {"value":"ES256"}, "userinfo_signed_response_alg": {"value": "ES256"} } }, "sub": "https://rp.eree.example.org" }
The process here is the one described in Section 6.2.
The OP applies the metadata policies on the leaf entity's metadata, all from the trust chain and comes up with:
{ "application_type": "web", "application_name": "EREE", "contacts": ["ops@eree.example.org"], "jwks_uri": "https://rp.eree.example.org/static/jwks.json", "redirect_uris": [ "https://rp.eree.example.org/authz_cb" ], "request_object_signing_alg": "ES256", "response_types": [ "code" ], "scope": [ "openid", "email" ], "token_endpoint_auth_method": "private_key_jwt", "token_endpoint_auth_signing_alg": "ES256", "userinfo_signed_response_alg": "ES256" }
Happy with the information in the client registration request the OP constructs its metadata policy and creates an entity statement by adding sub and authority_hints:
{ "authority_hints": { "https://eree.example.org": [ "https://eree.example.org" ] }, "exp": 1543931097, "iat": 1543326297, "iss": "https://op.umu.se", "metadata_policy": { "openid_relying_party": { "client_id": {"value": "FjeL1yco_Iu7VZoeDk"} } }, "sub": "https://rp.eree.example.org" }
The RP MUST collect the trust chain ending in the EREE trust anchor using the process described in Section 6, but refrain from processing the metadata. This since the entity statement issued by the EREE federation about the UmU OP are only valid for that entity and not for the EREE RP. The trust chain should only be used to verify that the entity statement actually comes from the expected OP.
If the RP is OK with what the OP decided on regarding the RP's metadata, then it will store this to be used in the following OpenID Connect protocol exchange with the OP.
In this example, the RP decided on one specific trust anchor before sending the registration request. If that was not the case but the RP had chosen to send a registration request with more than one authority hint, then this by time the RP could not apply any metadata polices to the metadata statement, since it would not know which to use.
Copyright (c) 2019 The OpenID Foundation.
The OpenID Foundation (OIDF) grants to any Contributor, developer, implementer, or other interested party a non-exclusive, royalty free, worldwide copyright license to reproduce, prepare derivative works from, distribute, perform and display, this Implementers Draft or Final Specification solely for the purposes of (i) developing specifications, and (ii) implementing Implementers Drafts and Final Specifications based on such documents, provided that attribution be made to the OIDF as the source of the material, but that such attribution does not indicate an endorsement by the OIDF.
The technology described in this specification was made available from contributions from various sources, including members of the OpenID Foundation and others. Although the OpenID Foundation has taken steps to help ensure that the technology is available for distribution, it takes no position regarding the validity or scope of any intellectual property or other rights that might be claimed to pertain to the implementation or use of the technology described in this specification or the extent to which any license under such rights might or might not be available; neither does it represent that it has made any independent effort to identify any such rights. The OpenID Foundation and the contributors to this specification make no (and hereby expressly disclaim any) warranties (express, implied, or otherwise), including implied warranties of merchantability, non-infringement, fitness for a particular purpose, or title, related to this specification, and the entire risk as to implementing this specification is assumed by the implementer. The OpenID Intellectual Property Rights policy requires contributors to offer a patent promise not to assert certain patent claims against other contributors and against implementers. The OpenID Foundation invites any interested party to bring to its attention any copyrights, patents, patent applications, or other proprietary rights that may cover technology that may be required to practice this specification.
The authors wish to acknowledge the contributions of the following individuals and organizations to this specification: Heather Flanagan, Misha Salle, Peter Schober, Michael Schwartz, and the JRA3T3 task force of GEANT4-2.
The following open issues remain to be addressed in this specification.
[[ To be removed from the final specification ]]
-08
-07
-06
-05
-04