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

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

Open
jogu opened this issue Sep 6, 2024 · 19 comments · May be fixed by #308
Open

Comments

@jogu
Copy link
Collaborator

jogu commented Sep 6, 2024

This is split out from openid/oid4vc-haip#133 :

For item 3 "what if verifier wants to pass multiple trust models, hoping one is supported by the wallet?"

This is implicitly mentioned in the WG10 ask since the 18013-5 request structure supports multiple RP authentication signatures.

Not having the ability to support multiple RP authentication signatures would mean that it's not possible to request documents that use different trust models, or request documents from issuers (e.g. regions) that have different trust models. Not having those abilities would be a significant limitation of the protocol.

Most of the consequences and possible solutions are similar to the ones mentioned in item 1, since having multiple RP authentication signatures seems to not be supported by the JAR specification.

The proposal addresses this by defining an array of verifier_authentication elements, each of which can contain a signature for a trust model. As the elements that are signed by these structures is dynamic (see item 1), this mechanism supports both the scenario of signing the encryption information, as well as also signing the vp_query.

This doesn't seem to be an mdl specific thing, there is a general problem that a verifier must submit a request to the wallet without knowing anything about the wallet, and, for example, obtaining an mdl regardless of where the mdl was issued/what wallet it is in means:

  1. The verifier may support more than one client_id_scheme and cannot know which one(s) the wallet(s) installed on the user's device support (e.g. for mdl USA states likely require x509_san_dns, some parts of the EU may well use OID Federation instead, and for non-mdl cases there may be even more deviation - this may also mean that the client_ids differ as, for example, there isn't a client_id that is valid for both x509_san_dns and openid federation)
  2. For each client_id_scheme, the verifier may have more than one way to authenticate, e.g. for x509_san_dns they may have a x509 certificate suitable for use in California and a second one suitable for use in the EU.
@jogu
Copy link
Collaborator Author

jogu commented Sep 6, 2024

And to copy across Torsten's comment with one idea for a solution:

This proposal is based on a discussion with @jogu @martijnharing @awoie @danielfett @bc-pi:

We would like to omit canonicalization of the request, that's why the basis of the proposal is to use the multi signature feature provided by the JWS JSON Serialization.

The OID4VP request

{
  "expected_origins": [
    "https://origin1.example.com",
    "https://origin2.example.com"
  ],
  "response_type": "vp_token",
  "response_mode": "w3c_dc_api.jwt",
  "nonce": "n-0S6_WzA2Mj",
  "client_metadata": {
    "vp_formats": {
      "vc+sd-jwt": {
        "sd-jwt_alg_values": [ "PS256" ],
        "kb-jwt_alg_values": [ "PS256" ]
      }
    },
    "jwks": {
      "keys": [
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
          "use": "enc",
          "kid": "1"
        }
      ]
    }
  },
  "presentation_definition" or "vp_query": {}
}

would not contain client_id and client_id_scheme. It would be put into the payload element of the JSON Serialization structure.

    const credential = await navigator.identity.get({
        digital: {
            providers: [{
                        protocol: "openid4vp",
                        request: {
                          "payload": "eyAiaXNzIjogImh0dHBzOi8...NzY4Mzc4MzYiIF0gfQ",
                          "signatures": [
                           {
                              "protected": "eyJhbGciOiJFUzI1NiJ9",
                              "header": {
                                "client_id": "987647789",
                                "client_id_scheme": "x509_san_dns"
                              },
                              "signature": "PFwem0Ajp2Sag...T2z784h8TQqgTR9tXcif0jw"
                          },
                          {
                              "protected": "eyJhbGciOiJFUzI1NiJ9",
                              "header": {
                                "client_id": "https://rp.federation.eu",
                                "client_id_scheme": "entity_id"
                             },
                            "signature": "irgtXbJGwE2wN4Lc...2TvUodsE0vaC-NXpB9G39cMXZ9A"
                          }
                        ]}

Every "signatures" object has the additional data for a certain RP authentication credential (protected or unprotected).
Underneath every "signatures" object, the "signature" object has the respective signature over "payload" and "protected".
"protected" could contain the response encryption key, so it is signed and authenticated.

Open Questions:

  • Would we adopt this for the traditional OID4VP flow and the DC API?
  • Would we adopt this and still support signed requests for a single client id with JWS Compact Serialization?

@paulbastian
Copy link
Contributor

paulbastian commented Sep 10, 2024

I support this.

Would we adopt this for the traditional OID4VP flow and the DC API?

yes

Would we adopt this and still support signed requests for a single client id with JWS Compact Serialization?

I prefer less code paths

@bc-pi
Copy link
Member

bc-pi commented Sep 12, 2024

Would we adopt this for the traditional OID4VP flow and the DC API?

Only in the context of the advanced request URI POST retrieval mode (sorry I can't remember the terminology).

Would we adopt this and still support signed requests for a single client id with JWS Compact Serialization?

Yes, keep JWS Compact Serialization.

@tplooker
Copy link
Contributor

tplooker commented Sep 16, 2024

In the sample provided by @jogu above, if the same key pair is used to sign the request in both the "client_id_scheme" = "x509_san_dns" and "client_id_scheme" = "entity_id" we have to sign the request twice and the client technically identifies in two different ways even though it is the same software.

Personally I think this will led to a piece of client software proliferating how they identify with wallets when in actual reality the only thing different is how the client is choosing to authenticate its request toward a wallet.

What I believe is a simpler model is the following.

const credential = await navigator.identity.get({
        digital: {
            providers: [{
                        protocol: "openid4vp",
                        request: {
                          "payload": "eyAiaXNzIjogImh0dHBzOi8...NzY4Mzc4MzYiIF0gfQ", // client_id in here
                          "signatures": [
                          {
                            "protected": "eyJhbGciOiJFUzI1NiJ9", // X5C parameter in here and even a verifier attestation in jwt header if required.
                            "signature": "irgtXbJGwE2wN4Lc...2TvUodsE0vaC-NXpB9G39cMXZ9A"
                          }
                        ]}

Whereby the protected header in this case contains the x5c parameter with an associated certificate chain for authenticating the signed request. The wallet processing the request can then decide how from the options provided it would like to authenticate the client, via resolution of its public keys from its metadata document OR via x509 certificate chain validation.

@jogu
Copy link
Collaborator Author

jogu commented Sep 17, 2024

@tplooker Can you explain how your approach covers scenario 2 above please, i.e. "For each client_id_scheme, the verifier may have more than one way to authenticate, e.g. for x509_san_dns they may have a x509 certificate suitable for use in California and a second one suitable for use in the EU."

@tplooker
Copy link
Contributor

In the event you are signing the request with different keys or associated certificate chains, then you would use the multiple signatures feature, but the request would be from one client_id identified in the payload.

@jogu
Copy link
Collaborator Author

jogu commented Sep 18, 2024

Right, I see, but then how is the client_id allocated to/selected by the wallet?

Are we saying the wallet can say "My client id is www.example.com but actually I'm going to authenticate to you as preregistered client id you allocated 24t3gerge5y346" or "my client id is www.example.com but actually I use did:key:z6MkjBWPPa1njEKygyr3LR3pRKkqv714vyTkfnUdP6ToFSH5 as my identifer to authenticate"?

@Sakurann
Copy link
Collaborator

for vanilla openid4vp, didn't we solve this with request_uri_method=post? where the wallet can tell its trust framework when obtaining the request object? and for openid4vp browser api profile, there is already an array where you can send multiple requests?

@jogu
Copy link
Collaborator Author

jogu commented Sep 19, 2024

for vanilla openid4vp, didn't we solve this with request_uri_method=post? where the wallet can tell its trust framework when obtaining the request object?

Unfortunately not, there's clauses like this:

The Client Identifier value in the client_id Authorization Request parameter and the Request Object client_id claim value MUST be identical. If the Authorization Request contains a client_id_scheme parameter, the client_id_scheme Authorization Request parameter and the Request Object client_id_scheme claim value MUST be identical. If any of these conditions are not met, the Wallet MUST terminate request processing.

which prevent the verifier changing it's client_id or client_id_scheme at the point where it knows what the wallet would support. Even if we fixed that, it doesn't really deal with a wallet that might support multiple trustframeworks and the verifier has no way to know which of the trustframeworks it might need to use to (say) get the user's driving license.

and for openid4vp browser api profile, there is already an array where you can send multiple requests?

I think it was definitely one of the original proposed approaches, but it was discarded and I'm struggling to remember the reason why. I think there was doubt as to whether browsers will actually support having multiple requests with "protocol": "oid4vp", and there was uneasiness with passing the request multiple times.

@Sakurann
Copy link
Collaborator

Unfortunately not, there's clauses like this:

The Client Identifier value in the client_id Authorization Request parameter and the Request Object client_id claim value MUST be identical. If the Authorization Request contains a client_id_scheme parameter, the client_id_scheme Authorization Request parameter and the Request Object client_id_scheme claim value MUST be identical. If any of these conditions are not met, the Wallet MUST terminate request processing.

which prevent the verifier changing it's client_id or client_id_scheme at the point where it knows what the wallet would support. Even if we fixed that, it doesn't really deal with a wallet that might support multiple trustframeworks and the verifier has no way to know which of the trustframeworks it might need to use to (say) get the user's driving license.

then my suggestion would be to consider change the text you quote to say that there is no need to include client_id in the first request from the verifier to the wallet, only request_uri. then when the wallet sends a POST to the request_uri, it can include the trust framework.

@jogu
Copy link
Collaborator Author

jogu commented Sep 21, 2024

then my suggestion would be to consider change the text you quote to say that there is no need to include client_id in the first request from the verifier to the wallet, only request_uri. then when the wallet sends a POST to the request_uri, it can include the trust framework.

We'd need to do this for the suggestion in this issues to work too.

Currently in request_uri_method=post there's not actually a defined way the wallet can communicate which trust framework(s) it supports. Probably we should actually define something if we want this to work. But following that path would mean the browser API and native oid4vp work in quite different ways, which might not be desirable (i.e. it makes the code more complex for implementors who want to support both).

@tplooker
Copy link
Contributor

Are we saying the wallet can say "My client id is www.example.com but actually I'm going to authenticate to you as preregistered client id you allocated 24t3gerge5y346" or "my client id is www.example.com but actually I use did:key:z6MkjBWPPa1njEKygyr3LR3pRKkqv714vyTkfnUdP6ToFSH5 as my identifer to authenticate"?

What I was meaning to say is "My client id is www.example.com and you have multiple ways you can trust me, heres how, I have one signature over the same request that tree's into this ecosystem and another into this ecosystem, im still the same software regardless".

@tlodderstedt
Copy link
Collaborator

then my suggestion would be to consider change the text you quote to say that there is no need to include client_id in the first request from the verifier to the wallet, only request_uri. then when the wallet sends a POST to the request_uri, it can include the trust framework.

We kept the client id in the initial request to allow the wallet, if possible, to apply a whitelist to the request_uri parameter. As far as I remember, we wanted to prevent attackers to let the wallet send requests to attacker selected locations.

@TomCJones
Copy link

so, what method does the wallet use to decide if the verifier is trusted sufficiently to display a consent message to the user?

@tlodderstedt
Copy link
Collaborator

As the question came up in the call today, here is the rational for this feature as fas as I remember from the initial discussions:
The idea is to provide RPs connected to different ecosystems with a way to send a single request that is good for these different ecosystems. One difference between these ecosystems is the client id/client id scheme/credential the RP would need to use. So having the ability to send a request with different credentials/signatures would enable these kind of RPs.

I hope that informs further discussions.

@TomCJones
Copy link

This points out the impossibility of using a URI for the client ID. URI as ID has never been a good idea, but it worked in the closed environment (a few big IDPs) where OIDC worked. It is important to get a verifier ID that is not dependant on the digital ecosystem, and specifically not dependant on the internet, but is designed to work with any pattern, anywhere in the world.

@Sakurann
Copy link
Collaborator

Sakurann commented Oct 28, 2024

discussed in a WG, agreed on the requirements/problem statement:

  • when the same wallet being able to hold multiple credentials coming from different trust frameworks, one request (one DCQL query) requesting two+ credentials that need two+ different trust frameworks to authenticate the RP.
    please focus this PR on this ^

there were opinions that above also applies when there is a request for one credential that might be one or the other trust framework, there were opinions that this one can be solved by multiple DCQL queries, or multiple requests over the Digital Credentials API. please open a separate issue on this (if there is not one already)

@David-Chadwick
Copy link
Contributor

Here is another idea, based on trust and truth. It is a variant of what we implemented. There are 4 options, trusted RP tells the truth about its trusted ID, Trusted RP makes a mistake and gives wrong untrusted ID (e.g. typo), Untrusted RP lies and gives a fake untrusted ID, Untrusted RP lies and gives a trusted ID.
Providing the trust infrastructure returns the trusted end point of the RP to the wallet, then the wallet is guaranteed to send the VP to the trusted RP.
In no situation does the RP's request need to be signed. The only one that potentially causes a problem is the last one. But this can be solved by making the RP's end point dynamic by having a URL with a query parameter (being a random ID that is provided on the initial request). The Trusted RP will provide a genuine RID, that its return end point will expect to receive along with the VP. The Untrusted RP will not know this RID, so when the wallet attempts to send the VP to the trusted RP, it will be rejected. We implemented this solution without the RID, because we did not consider the last case to be a serious security risk. But in an earlier conversation about this issue, I think it was Daniel who said it was a security risk that should be addressed. Hence the proposal now to add the RID to the request and response.

@tlodderstedt
Copy link
Collaborator

Here is the PR #308.

@jogu jogu added the ISO? label Dec 4, 2024
@Sakurann Sakurann linked a pull request Dec 5, 2024 that will close this issue
@Sakurann Sakurann added this to the Final 1.0 milestone Dec 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants