Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Multi RP Credentials/Authentication capability #308

Open
wants to merge 32 commits into
base: main
Choose a base branch
from

Conversation

tlodderstedt
Copy link
Collaborator

@tlodderstedt tlodderstedt commented Nov 5, 2024

This PR changes the way requests are signed to the Digital Credentials API from compact serialization (single RP) to JWS JSON Serialization (Multiple RPs).
Questions to the WG:

  • Do you want to keep (re-add) the JWS Compact Serialization to the Digital Credentials API profile?
  • Do we want the Multi RP capability to the traditional OID4VP flow?

resolves #248

openid-4-verifiable-presentations-1_0.md Outdated Show resolved Hide resolved
openid-4-verifiable-presentations-1_0.md Outdated Show resolved Hide resolved
Copy link
Member

@selfissued selfissued left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JWS Compact Serialization is supported by essentially all implementations, whereas many don't support the JSON Serialization, in part, because JWTs don't use it.

I believe switching to the JSON Serialization would present an unnecessary burden to developers and harm interoperability.

(For what it's worth, I believe that adding the JSON Serialization a dozen or so years ago at all was a mistake, but I realize that that's water under the bridge.)

@David-Chadwick
Copy link
Contributor

As I pointed out in the issue that this PR purports to resolve: when there is a trust infrastructure in place the request does not need to be signed as the wallet can determine the authenticity of the RP using the trust infrastructure. So we do not need multiple signatures and JSON serialisation.

@Sakurann
Copy link
Collaborator

@David-Chadwick well... the wallet determines the authenticity of the RP using the trust infrastructure based on the keys used to sign the request, that's why signature is crucial here.

@tlodderstedt
Copy link
Collaborator Author

As I pointed out in the issue that this PR purports to resolve: when there is a trust infrastructure in place the request does not need to be signed as the wallet can determine the authenticity of the RP using the trust infrastructure. So we do not need multiple signatures and JSON serialisation.

The profile supports unsigned requests. Would that be appropriate for your use cases?

@David-Chadwick
Copy link
Contributor

@Sakurann What you say is only one way of evaluating trust. The trust infrastructure can equally well decide if the RP is trustworthy or not based on its asserted name or ID. No keys are needed for this. If the asserted name/ID is deemed to be untrustworthy by the trust infrastructure, then the wallet knows not to trust the RP. If the name/ID is deemed to be trustworthy then there are two cases. 1. The RP is giving its true name, or 2. the RP is masquerading as a trustworthy RP. The latter can be protected against by the trust infrastructure providing the return URI of the RP for the wallet's response, and then the masquerading RP will not glean any information from the wallet. This is what we implemented. To prevent a DOS attack on the real trustworthy RP, we could add an extra protection feature, namely, the RP provides a random number on its request, which forms the query parameter on the wallet's reply to the return URI. The RP checks this parameter and if it is not recognised throws the wallet's response away.

@David-Chadwick
Copy link
Contributor

@tlodderstedt Yes, not requiring the RP's request to be signed solves the use case, and it also solves the problem of requiring multiple signatures to be on the request, because no signatures are actually needed. The RP can provide a set of names/IDs, one for each trust infrastructure that it is a member of. Then the wallet can match the presented trust infrastructures to the ones it is a member of, and where there is an intersection find out if the RP is trustworthy or not.

@tlodderstedt
Copy link
Collaborator Author

The JWS Compact Serialization is supported by essentially all implementations, whereas many don't support the JSON Serialization, in part, because JWTs don't use it.

I believe switching to the JSON Serialization would present an unnecessary burden to developers and harm interoperability.

(For what it's worth, I believe that adding the JSON Serialization a dozen or so years ago at all was a mistake, but I realize that that's water under the bridge.)

@selfissued The requirement we are asked to fulfill is to allow for multiple RP credentials being proven (through signatures) in a single request. We have extensively discussed this requirement in the hybrid workshop and agreed to support it. The design in the PR is, in my opinion, the simplest and most robust solution we can do it (alternative proposals involved canonicalization). So from my standpoint, what we can do is to also continue to support compact serialization. That's why I asked that question further up.

}
},
"presentation_definition": {...}
"dcql_query": {...}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why were the client_id, client_metadata, and jwks deleted? This change seems unrelated to the purpose of this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed related to this PR, as this data elements are RP specific. Consequently, the PR moves them into the RP credential specific elements of the request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should discuss where the client_id, client_metadata, and jwks claims should go when using the Compact Serialization. These would normally be claims - not header parameters.

I understand the reasoning for making them header parameters when using the JSON Serialization, but in my view, that's not the normal case, and we should make the normal case natural.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client_id, client_metadata, and jwks are request parameters when using the Compact Serialization. see examples/digital_credentials_api/signed_request_payload_compact.json

Comment on lines 2034 to 2037
The following request parameters MUST be present in the protected header of the respective signature object:

* `client_id`
* `client_metadata`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't defined header parameters, so this is weird. Why aren't these being sent as claims in the request?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because they are specific to a certain RP credential

"payload": "eyAiaXNzIjogImh0dHBzOi8...NzY4Mzc4MzYiIF0gfQ",
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NiJ9",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected a "kid" parameter - not just "alg".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean, see an header example (with kid and alg) below

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eyJhbGciOiJFUzI1NiJ9 decodes to {"alg":"ES256"}, which has no kid.

@tlodderstedt
Copy link
Collaborator Author

tlodderstedt commented Jan 14, 2025

Discussion in the WG meeting on the 14th of Jan:

  1. session transcript: there are two proposals on the table that were agreed to think through
  • @hlozi suggested to drop the Client Identifier from the session transcript (and have the origin only)
  • @awoie suggested to represent the relationship between a certain Client Identifier and the sub query / credential that Client Identifier is related to in the request and use that information to determine the expected Client Identifier for the corresponding Device Response object.
  1. response encryption: conclusion was to move the client_metadata parameter into the payload (again), i.e. there is one encryption key per request. (see a49eb50)

@awoie
Copy link
Contributor

awoie commented Jan 14, 2025

Discussion in the WG meeting on the 14th of Jan:

  1. session transcript: there are two proposals on the table that were agreed to think through
  • @awoie suggested to represent the relationship between a certain Client Identifier and the sub query / credential that Client Identifier is related to in the request and use that information to determine the expected Client Identifier for the corresponding Device Response object.

I don't think this is possible. I'm leaning towards removing the client_id from SessionTranscript but it raises the question why it is needed for other verifiable presentations in case of DC API.

@paulbastian
Copy link
Contributor

I summarized my views in this flow chart. I came to the conclusion that client_id is not needed in session transcript. Ultimately that would mean it makes sense to have aud in KB-JWT to represent origin in DC-API case.
image

@tlodderstedt
Copy link
Collaborator Author

I don't think this is possible. I'm leaning towards removing the client_id from SessionTranscript but it raises the question why it is needed for other verifiable presentations in case of DC API.

Can you please explain why this is not possible? We can add a "credentials" array to every client identifier specific "signature" object and use that to link the Client Identifier.

@tlodderstedt
Copy link
Collaborator Author

I summarized my views in this flow chart. I came to the conclusion that client_id is not needed in session transcript. Ultimately that would mean it makes sense to have aud in KB-JWT to represent origin in DC-API case.

That would mean the audience restriction would differ depending on the "transport". I would assume that makes implementations (at least in transition from traditional protocol to DC) harder. It also changes the level on which the restriction needs to be checked (client id/application vs. origin/transport).

@paulbastian
Copy link
Contributor

I summarized my views in this flow chart. I came to the conclusion that client_id is not needed in session transcript. Ultimately that would mean it makes sense to have aud in KB-JWT to represent origin in DC-API case.

That would mean the audience restriction would differ depending on the "transport". I would assume that makes implementations (at least in transition from traditional protocol to DC) harder. It also changes the level on which the restriction needs to be checked (client id/application vs. origin/transport).

That's already the reality for ISO mdoc format. I don't think it makes it much more complicated, because the input for credential formats, so mdoc and SD-JWT implementations don't need to change. The protocol implementations change anyway when people add DC-API, the change is how to build session_transcript/aud and that's what you stuff into the credential format library.

@tlodderstedt
Copy link
Collaborator Author

That's already the reality for ISO mdoc format.

Doesn't the device response contain the client id?

@paulbastian
Copy link
Contributor

paulbastian commented Jan 16, 2025

That's already the reality for ISO mdoc format.

Doesn't the device response contain the client id?

Not sure what you mean with device response.

client_id is present at two places in ISO18013-7

  • as Authorization Request parameter
  • in the session_transcript

The first is removed with DC-API and the second may be as well following my discussion line here in this thread.

and imo that makes sense, the client_id is kind of an origin equivalent, because OAuth couldn't achieve this without OS support.

Copy link
Member

@peppelinux peppelinux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can approve this PR because it enables an important flexibility in how a wallet evaluates the trust with an RP.

I have read and I found valuable the comments of other contributors, I have decided to approve it in the current status anyway to facilitate the process.

Regarding the question: would we be able to support both compact and json serialization, I want to answer that it would be in the scope of any good library detect and support both the formats, in full support of RFC 7515. I would not use openid4vp to limit the use of the serialization types (impl profile could decide this, if/when required).

@tlodderstedt
Copy link
Collaborator Author

The first is removed with DC-API and the second may be as well following my discussion line here in this thread.

Not sure what you mean, the client identifier is either present in the request (signed request) or determined from the origin (unsigned request).

and imo that makes sense, the client_id is kind of an origin equivalent, because OAuth couldn't achieve this without OS support.

They are different (in case of signed request) and the client identifier is more of a logical identifier that could be used from different origins.

### client_id

* Header Parameter Name: `client_id`
* Header Parameter Description: This header contains a Client Identifier.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Header Parameter Description: This header contains a Client Identifier.
* Header Parameter Description: This header contains a Client Identifier. A Client Identifier is used in OAuth to identify a certain client. It is defined in [@!RFC6749], section 2.2.

Comment on lines +2069 to +2076
{
"alg": "ES256",
"x5c": [
"MIICOjCCAeG...djzH7lA==",
"MIICLTCCAdS...koAmhWVKe"
],
"client_id": "x509_san_dns:rp.example.com"
}
Copy link
Collaborator

@Sakurann Sakurann Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specific proposal for disambiguating credential requested and credential_id

Suggested change
{
"alg": "ES256",
"x5c": [
"MIICOjCCAeG...djzH7lA==",
"MIICLTCCAdS...koAmhWVKe"
],
"client_id": "x509_san_dns:rp.example.com"
}
{
"alg": "ES256",
"x5c": [
"MIICOjCCAeG...djzH7lA==",
"MIICLTCCAdS...koAmhWVKe"
],
"client_id": "x509_san_dns:rp.example.com",
"credential_ids": [ <Array of strings each referencing a Credential requested by the Verifier> ]
}

Copy link
Collaborator

@Sakurann Sakurann Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credential_ids: REQUIRED. Array of strings each referencing a Credential requested by the Verifier that can be used in session transcript. In [DIF.PresentationExchange], the string matches the id field in the Input Descriptor. In the Digital Credentials Query Language, the string matches the id field in the Credential Query. If there is more than one element in the array, the Wallet MUST use only one of the referenced Credentials for session transcript

credential_id and client_id has to be 1:1 mapping. so that RP can determine a client_id based on the credential_id

Copy link
Collaborator

@Sakurann Sakurann Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternative proposal:
wallet puts used client_id in the response next to the credential:

{
  "my_credential": {
     “payload”: “eyJhbGci...QMA”,
     “client_id”: “asdf”
   }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what prevents MITM replaying the request RP sent through the DC API:

  • for unsigned requests, origin is in the returned credential because origin == client_id
  • for signed request, expected_origins is in the signed request that MITM cannot modify

Copy link
Member

@bc-pi bc-pi Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what prevents MITM replaying the request RP sent through the DC API:

  • for unsigned requests, origin is in the returned credential because origin == client_id

  • for signed request, expected_origins is in the signed request that MITM cannot modify

Thank you for this summary and apologies for struggling with the concept during the most recent DCP call. One nit just for posterity is that for unsigned request the client_id contains the origin and is prefixed (it doesn't strictly equal origin).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding the client_id as an optional parameter (array of strings) to the Credential Query? That would simplify things quite a bit imho for the request:

  • If client_id is not present in the Credential Query: you use only one client_id in the request and nothing changes to the status quo (without multi-rp)
  • If client_id is present, the wallet can directly match which client ids are possible for each requested credential and choose accordingly

The DCQL vp_token gets an optional parameter client_id as well that is only included if more than one client_id was provided in the request (the DCQL query) and not present otherwise --> nothing changes for normal flows.

That way nothing changes for simple flows, but mult-rp flows extend the normal data structures without any unnecessary duplication unless I am missing something.

@Sakurann
Copy link
Collaborator

WG discussion notes
issue: which client id to put in the session transcript - there are two proposals on the table that were agreed to think through:

  1. drop the Client Identifier from the session transcript (and have the origin only)
  2. represent the relationship between a certain Client Identifier and the sub query / credential that Client Identifier is related to in the request and use that information to determine the expected Client Identifier for the corresponding Device Response object.

arguments to go the second way:

  • this means no more audience restriction and that has been a fundamental security feature. much larger change since openid4vp specification currently mandates audience restrictions for all credential format. which means this has impact on other credential formats too.
  • second approach is also disambiguation

proposal and further discussion documented https://github.com/openid/OpenID4VP/pull/308/files#r1927297499

@Sakurann
Copy link
Collaborator

We don't have another DCP WG mtg before ISO virtual mtg next week. Having heard DCP WG discussion and having re-read ISO requirement, what I would like to offer as a way forward is to explain at the ISO virtual mtg that we are addressing ISO requirement of “mdoc authentication must be bound to the origin” not by including origin in the session transcript but by what is described below. If this gets accepted, we can remove origin from session transcript as an option, and conclude this circular discussion we have been having:

what prevents MITM replaying the request RP sent through the DC API:

  • for unsigned requests, origin is in the returned credential because origin == client_id
  • for signed request, expected_origins is in the signed request that MITM cannot modify

@David-Chadwick
Copy link
Contributor

Would it be better to rephrase the first bullet point to

for unsigned requests, the resulting credential is returned to the origin, because origin == client_id

@GarethCOliver
Copy link

Use Case: As a Verifier I want to request mDLs to verify KYC, and I accept mDLs issued in the EU, US.

US uses web PKI
EU uses an explicit trust framework

This seems like it would be a problem, unless we allow a mechanism to know to use the origin for particular returned credentials. (A US wallet may not be able to validate an EU signature, and so can't verify the signature over expected_origins)

@tlodderstedt
Copy link
Collaborator Author

Use Case: As a Verifier I want to request mDLs to verify KYC, and I accept mDLs issued in the EU, US.

US uses web PKI EU uses an explicit trust framework

This seems like it would be a problem, unless we allow a mechanism to know to use the origin for particular returned credentials. (A US wallet may not be able to validate an EU signature, and so can't verify the signature over expected_origins)

The question is whether the RP should be able to explicitly send an unsigned message or whether there should be a special logic to always try Web PKI in addition to the signed request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ISO_VirtualMeeting relevant for ISO OID4VP mdoc profile over DC API priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

How can verifiers that support multiple trust models/ecosystems know how to authenticate to the wallet?