Hashicorp Vault
The issuer API can leverage Hashicorp Vault's Transit Secrets Engine to hook into the "cryptography as a service" or "encryption as a service" features provided by Hashicorp Vault. With that, the issuer API can sign and verify credentials whilst keeping the secrets (signing keys) in an external secure environment.
If you are new to Hashicorp Vault make sure to checkout the guides here. The rest of this document assumes you already know how Vault works and have a transit secrets engine setup and running.
Setting Up Authentication Method
Once you have the Hashicorp Server up and running and enabled the transit secrets engine mentioned above, we need to either enable AppRole Authentication or User/Pass Authentication to use Hashicorp Vault with the walt.id issuer API.
Setup Policy
Before enabling and configuring authentication methods, a policy must be created to define the permissions and actions that authenticated users or roles are allowed to perform.
Create The Policy File
echo 'path "transit/*" {
capabilities = ["create", "update", "read", "delete", "list"]
}' > transit-policy.hcl
Check the Client Configuration
If you are configuring vault locally make sure that the client is configured to
communicate via HTTP. This can be achieved by setting the VAULT_ADDR
environment
variable.
export VAULT_ADDR=https://127.0.0.1:8200
Upload the Policy
vault policy write transit-policy transit-policy.hcl
# Expected result:
# Success! Uploaded policy: transit-policy
Setup Auth Method
To enable AppRole auth you have two options, the Web UI of Hashicorp Vault or the CLI:
- Navigate to
Access
. - Choose
Authentication Methods
. - Click on
Enable new method
. - Select
Generic / AppRole
. - Click
Enable method
.
Please note: You might want to disable lockout: select the created approle method, press "Configure", scroll to "User lockout configuration" and select "Disable lockout for this mount".
Configure AppRole
Add a Role
vault write auth/approle/role/my-role token_type=batch
# Additional parameters can include:
# secret_id_ttl, token_num_uses, token_ttl, token_max_ttl, secret_id_num_uses
Show Role ID
vault read auth/approle/role/my-role/role-id
# Save the value of role_id for later use.
Issue a Secret ID
vault write -f auth/approle/role/my-role/secret-id
# Save the value of secret_id for later use.
Associate Policy with AppRole
vault write auth/approle/role/my-role token_policies="transit-policy"
# Expected result:
# Success! Data written to: auth/approle/role/my-role
If you already have a token policy with the AppRole:
vault write auth/approle/role/my-role token_policies="existing-policy,transit-policy"
Key Creation
To create the key you can use the onboard endpoint provided by the issuer API or the CLI, UI or the API from Vault's Transit Secrets Engine.
The only thing that is important to note for the creation, that our system is only compatible with the following Key types offered by the Transit Secrets Engine:
- ed25519
Transit Secrets Engine key types full list.
Creation via Issuer API
Endpoint: /onboard/issuer
| API Reference
Example Request
curl -X 'POST' \
'https://issuer.portal.walt-test.cloud/onboard/issuer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"roleId": "my_role",
"secretId": "my_secret"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}'
Body
{
"key": {
"backend": "tse",
"keyType": "Ed25519",
"config": {
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"roleId": "my_role",
"secretId": "my_secret"
},
"namespace": "optional"
}
},
"did": {
"method": "key"
}
}
Body Parameters
key
backend
: String - Specifies the storage type of key. It can bejwk
(manged by you),TSE
(managed by Hashicorp Vault) and others. Learn more about different types here.keyType
: String - the algorithm used to generate the key. For Vault only ed25519 is possible.config
server
: URL - The endpoint of your Vault instance. Following the structure ofhttps://<yourHost>/v1/<pathOfTransitEngine>
, e.g. "https://vault.walt.id/v1/transit". By default the transit engine will live at /transit.auth
:- AppRole Auth:
roleId
: String - The Role ID for AppRole authentication.secretId
: String - The Secret ID for AppRole authentication.
- User/Pass Auth:
username
: String - The username for user/pass authentication.password
: String - The password for user/pass authentication.
- AccessToken Auth (Deprecated):
accessKey
: String - The access token for token-based authentication.
- AppRole Auth:
namespace
: String (optional) - The namespace within the Vault instance.
did
:method
: String - Specifies the DID method. It can be key, jwk, web, cheqd.
Example Response
The onboard/issuer endpoint will return an object containing both the generated key in JWK format and the related DID.
{
"issuerKey": {
"type": "tse",
"server": "http://127.0.0.1:8200/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k732628061",
"_publicKey": [
86,
-55,
122,
-77,
109,
-60,
61,
-40,
-122,
-57,
-101,
-53,
69,
-125,
12,
92,
-9,
89,
61,
-78,
116,
-76,
111,
-58,
-63,
-122,
-26,
79,
-110,
-32,
-73,
-98
],
"_keyType": "Ed25519"
},
"issuerDid": "did:key:z6MkkJ3JAhCpSGe5QD9UQ2WGzna7kcswa9ahMC2o3Dd1KRXb"
}
Properties
type
: String - the type of key can be either "tse" when using Hashicorp Vault or "jwk" when providing the key in full as JWK.server
: URL - The endpoint of your Vault instance. Following the structure ofhttps://<yourHost>/v1/<pathOfTransitEngine>
, e.g. "https://vault.walt.id/v1/transit". By default the transit engine will live at/transit
.auth
:- AppRole Auth:
roleId
: String - The Role ID for AppRole authentication.secretId
: String - The Secret ID for AppRole authentication.
- User/Pass Auth:
username
: String - The username for user/pass authentication.password
: String - The password for user/pass authentication.
- AccessToken Auth (Deprecated):
accessKey
: String - The access token for token-based authentication.
- AppRole Auth:
id
: String - the ID of the key in the Transit Engine._publicKey
(optional): Array - The public key can be fetched by the issuer API or directly provided, saving resources and reducing network requests._keyType
(optional): String - The key type can be fetched by the issuer API or directly provided, saving resources and reducing network requests.
Key Usage
Once you have successfully created a key that is one of the supported types listed above, you can use it in sign and issue operations offered by the issuer API.
If you've already had a look at
our /sign
, /issue
, /batchIssue
endpoints, you have seen that they all follow a similar request body structure, where the key that should be
used for signing credentials is provided via the issuerKey
property. Now instead of providing the key as JWK, we
provide
a reference to a key stored in Vault with the required parameters and access credentials.
Below you can see an example of
issuerKey
object referencing a key stored in Vault.
{
"type": "tse",
"server": "https://vault.walt.id/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k1474327600",
"_publicKey": [
-17,
83,
1,
26,
9,
-104,
-64,
-50,
48,
92,
-112,
-76,
7,
90,
-40,
-100,
72,
106,
-51,
-58,
64,
-26,
73,
121,
26,
-118,
39,
-8,
-25,
-34,
82,
37
],
"_keyType": "Ed25519"
}
Properties
type
: String - the type of key can be either "tse" when using Hashicorp Vault or "jwk" when providing the key in full as JWK.server
: URL - The endpoint of your Vault instance. Following the structure ofhttps://<yourHost>/v1/<pathOfTransitEngine>
, e.g. "https://vault.walt.id/v1/transit". By default the transit engine will live at/transit
.auth
:- AppRole Auth:
roleId
: String - The Role ID for AppRole authentication.secretId
: String - The Secret ID for AppRole authentication.
- User/Pass Auth:
username
: String - The username for user/pass authentication.password
: String - The password for user/pass authentication.
- AccessToken Auth (Deprecated):
accessKey
: String - The access token for token-based authentication.
- AppRole Auth:
id
: String - the ID of the key in the Transit Engine._publicKey
(optional): Array - The public key can be fetched by the issuer API or directly provided, saving resources and reducing network requests._keyType
(optional): String - The key type can be fetched by the issuer API or directly provided, saving resources and reducing network requests. ::
Example Issuance Request
Below you can see example issuance request
to jwt/issue
using
a key created in the Vault Transit Secret Engine to
sign the credential.
curl -X 'POST' \
'https://issuer.portal.walt.id/openid4vc/jwt/issue' \
-H 'accept: text/plain' \
-H 'statusCallbackUri: https://example.com/$id' \
-H 'Content-Type: application/json' \
-d '{
"issuerKey": {
"type": "tse",
"server": "https://vault.walt.id/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k-1481938705"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "<uuid>",
...
}
}'
Header Parameters
statusCallbackUri
: URL - Receive updates on the created issuance process, e.g. when a credential was successfully claimed. The parameter expects a URL which can accept a JSON POST request. The URL can also hold a$id
, which will be replaced by the issuance session id. For example:https://myurl.com/$id
,https://myurl.com
orhttps://myurl.com/test/$id
Expand To Learn More
Body
The data send to the provided URL will contain a JSON body:
id
: String - the issuance session idtype
: String - the event typedata
: JsonObject - the data for the event
Event Types
Possible events (event types) and their data are:
resolved_credential_offer
with the credential offer as JSON (in our Web Wallet: called when the issuance offer is entered into the wallet, but not processing / accepted yet)requested_token
with the issuance request for the token as json object (called for the token required to receive the credentials)
Credential issuance (called for every credential that's issued (= requested from wallet))
jwt_issue
withjwt
being the issued jwtsdjwt_issue
withsdjwt
being the issued sdjwtbatch_jwt_issue
withjwt
being the issued jwtbatch_sdjwt_issue
withsdjwt
being the issued sdjwtgenerated_mdoc
withmdoc
being the CBOR (HEX) of the signed mdoc
To allow for secure business logic flows, if a callback URL is set, and it cannot be reached, the issuance will not commence further (after that point). If no callback URL is set, the issuance logic does not change in any way.
Body
As you can see for the property issuerKey
we only provided the required parameters of the TSE Key Reference Object
described above and left out _publicKey
and _keyType
{
"issuerKey": {
"type": "tse",
"server": "https://vault.walt.id/v1/transit",
"auth": {
"roleId": "b50427bd-5070-0bec-3a00-dfe470ba349d",
"secretId": "22ffc0f4-c308-e6d6-f5a4-246cd89bc17c"
},
"id": "k-1481938705"
},
"issuerDid": "did:key:z6MkjoRhq1jSNJdLiruSXrFFxagqrztZaXHqHGUTKJbcNywp",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context.json"
],
"id": "urn:uuid:THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION (see below)",
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"name": "JFF x vc-edu PlugFest 3 Interoperability",
...
},
"mapping": {
"id": "<uuid>",
...
},
"standardVersion": "DRAFT13"
}