- From: Mike Varley <mike.varley@securekey.com>
- Date: Wed, 21 Jul 2021 18:03:11 +0000
- To: Orie Steele <orie@transmute.industries>, Justin Richer <jricher@mit.edu>
- CC: W3C Credentials CG <public-credentials@w3.org>
- Message-ID: <YT1PR01MB30999833986AFAB167C5665DE4E39@YT1PR01MB3099.CANPRD01.PROD.OUTLOOK.COM>
Hi Orie, Thanks Justin, these examples are very helpful. It seems like the primary value of RAR is extensibility. Objects are easier to extend than strings. I want to understand the security tradeoffs by leveraging these extensibility features. Your identifier security parameter example is very helpful. In that case, are you not exposing that identifier to the AS? Why does the AS need to know which DIDs a resource server can issue/present on behalf of? I presume Justin was providing an example, there is no requirement for an ‘identifier’ field – it’s merely a capability this API could take advantage of. Typically in identity use cases the AS is already aware of identifiers, and in fact is the manager of these identifiers (subject IDs and PAIs in OpenID Connect, for example). Identifiers are used by the AS to provide context to the RS around the subject of the authorization – “when you see this token, I’m talking about Bob”… is an oversimplification but you get the idea I hope. It would seem safe to make use of the extensibility as long as you don't leak subject identifiers to the AS, can you comment on these design considerations? I would agree that not leaking identifiers is A Good Thing; consider in the personal ID space authorization is provided by a subject directly, the identifier is not ‘leaked’ but rather a desired co-ordinating piece of info. In other use cases where an access grant by an AS is for a _service_, and the subjects of the credentials is relevant between the service and the RS (VC-HTTP-API), then agreed that AS does not need this information and it can be omitted. There is another consideration on this second (service-to-service) use case though: What if the RS requires the AT to be bound to a service’s DID? So that the RS will only accept ATs that are accompanied by the services DID signature? This mechanism has not been spec’d out AFAIK (other mTLS and DPOP specs exist, but nothing DID specific?) but anyway, in this case the AS may want to understand the identifier of the Service requesting access will use when communicating with the RS, if the use case calls for it. All good food for thought. MV I'm on my phone sorry for any spelling. OS On Wed, Jul 21, 2021, 9:42 AM Justin Richer <jricher@mit.edu<mailto:jricher@mit.edu>> wrote: On yesterday’s call, we briefly discussed the applicability of RAR for the VC HTTP API, and I wanted to continue that conversation with some additional concrete points. Apologies in advance for the long email, but there’s a lot to untangle here as it seems a number of people have the wrong idea. First, what are we proposing? I am suggesting, strongly, that when defining this API, the group also define the different ways to control access to it. Which is to say, what actions can you do, what kinds of data do you want to switch on — in short, we’ve got an API, how do we want to slice it? I am explicitly saying that we do not define anything about the process of getting a token (so, specifying client credentials is a terrible idea) or how to process the token (so specifying token formats is also a terrible idea) because these don’t affect the API. What does affect the API is the availability of different kinds of actions for a specific call. What are the things that I can DO here? This is what OAuth 2 invented scopes for, instead of the OAuth 1 or HTTP Basic method of “you just get everything”. So, why not just use parameterized scopes? It seemed like a good idea when I first invented it a decade ago: https://blue-button.github.io/blue-button-plus-pull/#scopes or when it got pulled into other efforts like https://openid.net/specs/openid-heart-fhir-oauth2-1_0-2017-05-31.html … and Orie even suggested the following set of parameterized scopes for this API: 'create:credentials': Grants permission to create credentials 'derive:credentials': Grants permission to derive credentials 'create:presentations': Grants permission to create presentations 'verify:presentations': Grants permission to verify presentations 'exchange:presentations': Grants permission to exchange presentations So what’s the problem? I can say with full confidence after years of experience building and deploying systems to support parameterized scopes like this that they are fragile, awkward, and lead to insecure corner cases. For example, am I supposed to parse these strings to find the bits on either side of that colon? Am I allowed to make up my own combinations of elements, so for example do I get “derive:presentations” from that set above, for free? What if I am protecting more than one API and they use a different separator? Or what if there’s just a plain old namespace collision and someone else has “exchange:presentations” on their unrelated API that that the AS is protecting at the same time? And what if someone has an additional dimension they need to convey, like the identifier for a specific account— does that mean we add a parameter to the string above, or do we need a separate scope to carry this, and how do we combine all of that? While all of these questions can be answered, the answers make something that seems simple on the surface spiral out of hand very quickly. I know this because I’ve seen it time and again, and this experience is exactly what led me to create what has become RAR. So let’s talk about how we’d address the above actions in RAR. First, please note that I’m going to be fast and loose with JSON syntax here because I’m typing it out by hand, so don’t try to compile this. :) Second, for simplicity of following this argument, let’s say that all five actions above are inside a single RAR type value, and we’ll just use “vha" as the stand-in for that value (in reality it would be a URI, and there might be multiple for a given API family like VC HTTP API). Picking apart the pieces above, we’ve got two elements that fit fairly well into the “actions” and “datatypes” fields in a RAR request type: actions: [ create, derive, verify, exchange ] datatypes: [ credentials, presentations ] Since RAR is about combining different dimensions into a single request, we can easily get to the kind of expressions above. Let’s say I want to create credentials and permissions, but nothing else — I can ask for the following value: { type: “vha”, actions: [ create ], datatypes: [ credentials, presentations ] } Or if I want to create and derive credentials but do nothing with presentations, I can do: { type: “vha”, actions: [ create, derive ], datatypes: [ credentials ] } And how about a more complex cross-product? Let’s say I want to create and derive credentials but only exchange presentations. All RAR requests are in an array, just like OAuth 2 scopes are fundamentally an array, so that request would look like this: [ { type: “vha”, actions: [ create, derive ], datatypes: [ credentials ] }, { type: “vha”, actions: [ exchange ], datatypes: [ presentations ] } ] Unlike with scopes, the extent of each aspect is clear to developers of both the API and the clients of the API. It’s very clear what I’m asking to do and what I’m doing it to, without having to define or parse or understand any kind of VC-HTTP-API-specific syntax to cram into an existing field. This gets even better with GNAP because you can ask for multiple different-scoped tokens at the same time. And what if someone requests a confusing or bad combination? { type: “vha”, actions: [ create, derive ], datatypes: [ credentials, presentations ] } RAR clearly defines each object as the union of all items within it, so anything processing this would be able to tell that “derive” and “presentations” doesn’t fit together and this is an invalid request. There’s no need to guess and no need to put in special casing. Your implementation would be configured with viable combinations, and anything that falls outside of that is easily detected. It gets even clearer if we want to talk about other dimensions not captured in the example scopes above. If I want to do this for a specific account? We can define a security identifier in this request to contain that information: { type: “vha”, actions: [ create, derive ], datatypes: [ credentials ], identifier: did:example:12345 } Already we’re doing something that would be difficult with scope strings. And what if we are protecting multiple APIs that use similar syntax? They sit in separate namespaces so there is no chance for confusion. [ { type: “vha”, actions: [ create, derive ], datatypes: [ credentials ] }, { type: “TOTALLY-NOT-VHA”, actions: [ create, derive ], datatypes: [ credentials ], not-a-vha-attribute: do-some-weird-thing } ] What does this mean for the VC HTTP API itself? It means that each call defined as part of the API would have a section like: Security: Type: “vha” Action: “create” Datatype: “credentials" … and that’s all the detail that would be needed. The core part of the spec would say something like “use the fields defined in the ’security’ section to make a RAR or GNAP request”, and that only applies if you’re doing OAuth 2 (with any flow) or GNAP (with any mode). You’re still free to protect the API with whatever fancy pants thing you want, but even then the Security section will lay out the core dimensions that each call is differentiated on. So yes, it’s possible to pull this off with scope strings. I’ve done things more complex than this and have seen some really crazy stuff like using a fully-qualified query language as the scope string value. However, what I’m trying to convince this group is that there is a much simpler way and that’s by embracing the object structures that RAR lets you define, to talk about the dimensions of access that you want to allow or disallow. That discussion is going to happen anyway, and is probably ALREADY happening. I am saying this group should embrace that challenge as part of the protocol definition, and do so sooner rather than later. If we wait, it’ll end up a mess of proprietary solutions that don’t want to change, and people will dig in their heels even more for either a loose spec or patchwork of things so implementations don’t have to change. What I’m calling for is structure for interoperability and compatibility, in a way that’s a very, very light lift on the spec itself and would in fact help define the elements of the spec. Believe me — I’ve been down this road before and I am hoping that this group could learn from my mistakes instead of repeating them. — Justin This email and any attachments are for the sole use of the intended recipients and may be privileged, confidential or otherwise exempt from disclosure under law. Any distribution, printing or other use by anyone other than the intended recipient is prohibited. If you are not an intended recipient, please contact the sender immediately, and permanently delete this email and its attachments.
Received on Wednesday, 21 July 2021 18:03:38 UTC