vc-api
vc-api copied to clipboard
Document Instance Configuration in the VC API
@dlongley I get the impression from your comments that it might be easier to see your perspective if we could see what you wish the VC API Issuer endpoints looked like, if we were to build them from scratch, focused only on your "business use cases" and not at all focused on "demonstrating interop".
Would you be willing to sketch out in comments what you would build if we could start over here?
Addressing issues like:
- How many endpoints?
- How many issuers / dids / keys / suites?
- Optional or required fields related to extensions (like revocation)
- API Security
We might be able to leap frog the current APIs with a clearer picture of your complete vision, and I am concerned we won't be able to make good progress trying incrementally adjust from where we are today.
A rough outline of the issuing API:
Create an issuer instance
/issuers
POST an issuer configuration to create an issuer instance:
{
// used to detect update conflicts
sequence: 0,
// indicates the suite used by this issuer instance;
// create a different instance for a different suite
issueOptions: {
suiteName: 'Ed25519Signature2020'
},
// indicates status list options for every VC issued
// by this instance; create a different instance for
// different options
statusListOptions: [{
type: 'StatusList2021',
statusPurpose: 'revocation',
suiteName: 'Ed25519Signature2020'
}, /* other statuses such as 'suspended' */],
// from here on down -- standardize or not?
// optional / perhaps implementation specific...
// used to establish root controller for zcap authz model
controller: '<DID of root of control for the instance>',
// other properties to enable billing / metering use cases, etc.
meterId: '<some URL identifying a metering solution>',
// other properties to increase security
ipAllowList: ['127.0.0.1/32']
// zcaps to access the necessary keys and storage to perform
// the instance's tasks...
zcaps: {
// zcap to access EDV storage for this issuer instance
edv: { ... },
// zcap to access encrypted EDV indexes for this issuer instance
hmac: { ... },
// zcap to decrypt EDV documents for this issuer instance
keyAgreementKey: { ... },
// zcap to access the assertion method key for signing VCs;
// must be validated to be usable with `issuerOptions.suiteName`
// upon config creation or update
assertionMethod: { ... }
}
// the above zcap method could be replaced or augmented with
// oauth credentials / access tokens to enable the issuer instance
// to access the keys / storage it needs; the issuer instance could
// also have *direct* access to keys and storage -- this is
// implementation specific
}
The issuer server must auto-generate an id
for the configuration and return the result.
Result:
{
id: 'https://example.com/issuers/zmn2343n2m2312312',
// same properties as above...
}
Update an issuer instance
/issuers/<issuer instance ID>
POST an issuer configuration to update an issuer instance. Same as above but the sequence
must be 1
more than the existing configuration or a conflict error will be raised.
Get an issuer instance config
/issuers/<issuer instance ID>
GET an issuer configuration. Returns the config mentioned above.
Add a context to an issuer instance
/issuers/<issuer instance ID>/contexts
POST a JSON-LD context document to add it to the set of contexts that the issuer instance will securely load when issuing VCs. The issuer instance will only load standard contexts (VC data model context v1, cryptosuite contexts) by default; this endpoint allows it to securely load any other context it is configured to load.
{
// the URL for the context
id: 'https://test.example/v1',
// the context document
context: {
'@context': {
term: 'https://test.example#term'
}
},
// used to detect update conflicts
sequence: 0
}
Update a context in an issuer instance
/issuers/<issuer instance ID>/contexts/<URI encoded url identifying the context>
POST a JSON-LD context document to update one of the contexts that the issuer instance will securely load when issuing VCs.
{
// the URL for the context
id: 'https://test.example/v1',
// the context document
context: {
'@context': {
term: 'https://test.example#term'
}
},
// must be `1` more than the existing sequence or
// a conflict error will be raised
sequence: 1
}
Get a context from an issuer instance
/issuers/<issuer instance ID>/contexts/<URI encoded url identifying the context>
GET a JSON-LD context stored with an issuer instance.
Issue a VC from an issuer instance
/issuers/<issuer instance ID>/credentials/issue
POST the VC to issue. This is similar to existing API but without parameterized options.
Change the status of a VC issued from an issuer instance
/issuers/<issuer instance ID>/credentials/status
POST the new credential status for a VC. This is similar to existing API.
A rough outline of the verifying API:
Create a verifier instance
/verifiers
POST a verifier configuration to create a verifier instance:
{
// used to detect update conflicts
sequence: 0,
// from here on down -- standardize or not?
// optional / perhaps implementation specific...
// used to establish root controller for zcap authz model
controller: '<DID of root of control for the instance>',
// other properties to enable billing / metering use cases, etc.
meterId: '<some URL identifying a metering solution>',
// other properties to increase security
ipAllowList: ['127.0.0.1/32']
// zcaps to access the necessary storage to perform
// the instance's tasks...
zcaps: {
// zcap to access EDV storage for this verifier instance
edv: { ... },
// zcap to access encrypted EDV indexes for this verifier instance
hmac: { ... },
// zcap to decrypt EDV documents for this verifier instance
keyAgreementKey: { ... },
}
// the above zcap method could be replaced or augmented with
// oauth credentials / access tokens to enable the verifier instance
// to access the storage it needs; the verifier instance could
// also have *direct* access to storage -- this is
// implementation specific
}
The verifier server must auto-generate an id
for the configuration and return the result.
Result:
{
id: 'https://example.com/verifiers/zmn2343n2m2312312',
// same properties as above...
}
Update a verifier instance
/verifiers/<verifier instance ID>
POST a verifier configuration to update a verifier instance. Same as above but the sequence
must be 1
more than the existing configuration or a conflict error will be raised.
Get a verifier instance config
/verifiers/<verifier instance ID>
GET a verifier configuration. Returns the config mentioned above.
Add a context to a verifier instance
/verifiers/<verifier instance ID>/contexts
POST a JSON-LD context document to add it to the set of contexts that the verifier instance will securely load when issuing VCs. The verifier instance will only load standard contexts (VC data model context v1, cryptosuite contexts) by default; this endpoint allows it to securely load any other context it is configured to load.
{
// the URL for the context
id: 'https://test.example/v1',
// the context document
context: {
'@context': {
term: 'https://test.example#term'
}
},
// used to detect update conflicts
sequence: 0
}
Update a context in a verifier instance
/verifiers/<verifier instance ID>/contexts/<URI encoded url identifying the context>
POST a JSON-LD context document to update one of the contexts that the verifier instance will securely load when verifying VCs.
{
// the URL for the context
id: 'https://test.example/v1',
// the context document
context: {
'@context': {
term: 'https://test.example#term'
}
},
// must be `1` more than the existing sequence or
// a conflict error will be raised
sequence: 1
}
Get a context from a verifier instance
/verifiers/<verifier instance ID>/contexts/<URI encoded url identifying the context>
GET a JSON-LD context stored with an issuer instance.
Get a challenge from a verifier instance
/verifiers/<verifier instance ID>/challenges
POST an empty object to get a new challenge for use in a presentation. This feature should be made available to users of the API so that they do not need to securely create and maintain their own challenges.
Result:
{
challenge: <some string>
}
Verify a single VC via a verifier instance
/verifiers/<verifier instance ID>/credentials/verify
POST the VC to verify. This is similar to existing API.
Verify a VP via a verifier instance
/verifier/<verifier instance ID>/presentations/verify
POST the VP to verify. This should include a challenge created via the challenge API to prevent replay attacks.
The above APIs are expected to be fully compatible with both (concurrently or separately) zcaps and oauth2.
The zcaps model involves setting a root controller via the configuration of each instance. The root zcap is urn:zcap:root:<authority>/<issuers|verifiers>/<issuer|verifier instance ID>
and its controller
will be auto-populated via the controller
from the instance config. This root zcap can be delegated and attenuated for any API endpoint that stems off of the instance ID URL, allowing granular control and fully decentralized delegation via any API endpoint. Issuer and verifier configs can include their own zcaps for accessing storage / keys via other (to be) standardized interfaces such as EDVs and WebKMS.
The oauth2 model involves setting an authz server for the issuer / verifier instance to use to perform authz checks. Scopes should be based on the API endpoints and, ideally, access tokens would be limited to one per scope, but this is not the usual oauth2 model (which tends to have more ambient authority w/single access tokens containing all accessible scopes). Oauth2 credentials and / or access tokens could be set in the instance configuration to allow instances themselves to gain access to keys / storage as needed.
Implementations may decide to provide access to keys / storage for particular instances however they like, the above just outlines that it's also possible to delegate these things out to particular instances. This design allows issuer instances, for example, to be given limited or fit-for-purpose authority and for blast damage to be limited.
The way the capabilities are given to instances above (or through oauth2) may not be a target for standardization at this time depending on implementer interest.
TrustBloc supports a similar concept of registering 'profiles' for issuer/verifier VC API implementation, but it is very rudimentary. https://github.com/trustbloc/edge-service/blob/main/docs/vcs/issuer/README.md#create-a-profile https://github.com/trustbloc/edge-service/blob/main/docs/vcs/verifier/README.md#verifier-vcs
And the {profileID} is included in the API path.
I'm not 100% sure the two are aligned, just drawing out some similarities.
@OR13, added GET
calls to get the instance configs. I just accidentally omitted those before.
We may want a separate set of endpoints for setting / getting oauth2 client credentials / access tokens for the instance to access keys / storage (similar to how contexts are handled above) rather than storing them in the configuration object to allow client applications to read the config (without security issues). The zcap approach doesn't have the same problem as they require proof of possession to invoke. We may also just want to express "client only" configuration information in a separate endpoint and just grant client apps the ability to read that.
The following updates need to be made to the spec:
- Introduce the concept of there being "instances", which is what the APIs hang off of.
- If you want to have instances/configurations, the APIs above provide one optional mechanism one could use to configure the instances.
- If implementers don't implement the instances API, one could use a different mechanism to configure instances (or just have a static implementation that doesn't have the concept of multiple instances)
A note on my last comment:
We may want a separate set of endpoints for setting / getting oauth2 client credentials / access tokens for the instance
An OAuth2 Authorization Server Issuer Base or Config (w/well-known path) URL can be provided in the instance config to enable OAuth2 support for using the APIs exposed off of that particular instance.
I suggest closing this issue, the APIs seem to have ossified beyond the value of a greenfield exercise.
The group discussed this in 2023-03-21:
@dlongley noted that the endpoints that we have today are expected to hang off of instances and those instances have a configuration associated with them that has information/state associated with them to do their jobs. Not all that information can be provided as parameters from client, so concept of instances should be explained in spec, here are APIs that you can use, then we can point to text that was noted in this issue. We are not looking at changing API significantly, we already depend on these instances, but make a bad job of making that clear to people. This is about clarifying that APIs hang off of instances that have been configured so the endpoints can achieve certain use cases. Not all parameters can be passed by a client.
Raise a PR that:
- Document the concept of instances
- The APIs don't work unless you have configuration/state behind them. APIs defined in specs are attached to specific instances that are configured with whatever state that is needed, in addition to what client provides in a request.
- Instance configuration is setup by an administrator, where the client calling the instance might not have the capability to configure an instance, or be aware of the setup that the administrator did on the instance. Administration endpoints for configuring instances could be provided by implementations but are not necessary to expose as HTTP APIs (for example, configuration can be done through UI and config files).
- We might want to reconfigure the language around endpoints, use "instance" language... e.g., "When a client calls an endpoint on a particular instance, the instances uses the configuration and options provided by the client to execute the action."