diff --git a/demo/AliceGetsAPhone.md b/demo/AliceGetsAPhone.md index 92a1632ff6..4e7aecf0a5 100644 --- a/demo/AliceGetsAPhone.md +++ b/demo/AliceGetsAPhone.md @@ -1,6 +1,6 @@ # Alice Gets a Mobile Agent! -In this demo, we'll again use our familiar Faber ACA-Py agent to issue credentials to Alice, but this time Alice will use a mobile wallet. To do this we need to run the Faber agent on a publicly accessible port (we'll use Play With Docker), and Alice will need a compatible mobile wallet. We'll provide pointers to where you can get them. As well, we'll also show how you can run through this sequence with credential revocation activated, so you can see how the mobile wallets handle that. +In this demo, we'll again use our familiar Faber ACA-Py agent to issue credentials to Alice, but this time Alice will use a mobile wallet. To do this we need to run the Faber agent on a publicly accessible port, and Alice will need a compatible mobile wallet. We'll provide pointers to where you can get them. This demo also introduces revocation of credentials. # Contents @@ -23,158 +23,79 @@ In this demo, we'll again use our familiar Faber ACA-Py agent to issue credentia ## Getting Started -To get started with this demo using a browser, go to [Play With Docker](https://labs.play-with-docker.com/), start a terminal session. Don't know about Play with Docker? Check [this out](https://github.com/cloudcompass/ToIPLabs/blob/master/docs/LFS173x/RunningLabs.md#running-on-play-with-docker) to learn more. Once in your terminal session, use git to get this repository cloned on the instance. +This demo will be run on your local machine and demonstrate credential exchange and proof exchange as well as revocation with a mobile agent. -```bash -git clone https://github.com/hyperledger/aries-cloudagent-python -cd aries-cloudagent-python/demo +If you are not familiar with how revocation is currently implemented in Hyperledger Indy, [this article](https://github.com/hyperledger/indy-hipe/tree/master/text/0011-cred-revocation) provides a good background on the technique. A challenge with revocation as it is currently implemented in Hyperledger Indy is the need for the prover (the agent creating the proof) to download tails files associated with the credentials it holds. -``` +### Get a mobile agent Of course for this, you need to have a mobile agent. To find, install and setup a compatible mobile agent, follow the instructions [here](https://github.com/bcgov/identity-kit-poc/blob/master/docs/GettingApp.md). -### Running Locally in Docker - -Unlike the ACA-Py to ACA-Py Alice/Faber demos we've run in the past, the ACA-Py to Mobile Alice scenario has an extra requirement—the ACA-Py inbound agent-to-agent transport must be publicly accessible. This is a given with Play With Docker, but trickier to do when you are running on your local machine. It can be done with something like ngrok, but we're leaving that as an exercise for the user. If you do get it working and have instructions, please drop us a note or make a PR against this document. - -### Testing with Revocation - -Want to run this with revocation active? It's an option! Throughout the process we'll call out differences if you want to use revocation. Note that to use revocation with these instructions, you need a pretty good understanding of GitHub, so be warned. If this is your first run through this demo, we recommend skipping revocation and trying it on a subsequent run through. Jump down to here if you **aren't** going to use revocation. - -Still here? There are some extra things you need to do to run with revocation enabled. If you are not familiar with how revocation is currently implemented in Hyperledger Indy, [this article](https://github.com/hyperledger/indy-hipe/tree/master/text/0011-cred-revocation) provides a good background on the technique. A challenge with revocation as it is currently implemented in Hyperledger Indy is the need for the prover (the agent creating the proof) to download tails files associated with the credentials it holds. Further, when using a mobile agent, mobile OSes require that all HTTP requests go over HTTPS, which Play with Docker doesn't support. So for this exercise, we're going to create our own "tails server" on the fly using GitHub. - -#### Setting up a Public GitHub Tails Server - -There are a number of ways to get the tails file posted on an HTTPS web service. We're going to use GitHub. If you ideas for other/easier ways to do this, please let us us know. - -We're going to use GitHub by having you create a `tails-files` repo in your personal GitHub account. We're assuming that you have your own GitHub account and repo, and that you can push updates to it. Do **NOT** do this from your Play with Docker terminal session as your GitHub credentials are not available in that environment and you should **NOT** make them available. - -1. Create your tails server by creating a public github repository called `tails-files` in your personal GitHub account. - -
- Click here to view screenshot (github.com) - Github repo -
- -2. On your **local machine** where you have a git/github setup, open a terminal session and clone the repo to your local file system. To clone the repo, do the following, **replacing** `# NAME` with your own GitHub id: +### Run an instance of von-tails-server in docker -> NOTE: Do not use Play With Docker for this because you must **NEVER** enter your credentials into a Play With Docker terminal session. -> +For revocation to function, we need another component running that is used to store what are called tails files. In a project directory, run: ```bash -git clone https://github.com/# NAME/tails-files.git -cd tails-files - +git clone git@github.com:bcgov/von-tails-server.git +cd von-tails-server/docker +./manage build && ./manage start GENESIS_URL=http://test.bcovrin.vonx.io/genesis ``` -You will get an error if you forget to replace `# NAME` with your GitHub account name. - -Keep this terminal session open, as we'll use it later to retrieve the tails files from Play With Docker and commit them to GitHub. - -That's enough for getting started with revocation. On with the instructions! +This will run the required components for the tails server to function and make a tails server available on port 6543. -## Run `faber` With Extra Parameters +### Expose services publicly using ngrok -The first step is to start the ACA-Py Faber agent. This is done differently if you are using revocation or not. - -If you are not using revocation, use this command: +Since the mobile agent will need some way to communicate with the agent running on your local machine in docker, we will need to create a publicly accesible url for some services on your machine. The easiest way to do this is with [ngrok](https://ngrok.com/). Once ngrok is installed, create 2 tunnels to your local machine: ```bash -LEDGER_URL=http://test.bcovrin.vonx.io ./run_demo faber --events +ngrok http 8020 ``` -If you are running and including revocation, use this command, replacing `# NAME` with the GitHub account in which you created the tails-file repo: - ```bash -PUBLIC_TAILS_URL=https://github.com/# NAME/tails-files/raw/master TAILS_FILE_COUNT=10 LEDGER_URL=http://test.bcovrin.vonx.io ./run_demo faber --events --revocation +ngrok http 6543 ``` -If this generated an error, check if you replaced `# NAME` with that of your GitHub account. - -The `Preparing agent image...` step on the first run takes a bit of time, so while we wait, let's look at the details of the commands. Running Faber is similar to the instructions in the [Aries OpenAPI Demo](./AriesOpenAPIDemo.md) "Play with Docker" section, except: - -- We are using the BCovrin Test network because that is a network that the mobile agents can be configured to use. -- We are running in "auto" mode, so we will no manual acknowledgements. -- Play with Docker exposes the Agent's' port (in this case port 8021 of the container) on a public URL that the mobile app can access. -- The revocation related changes: - - The `PUBLIC_TAILS_URL` environment variable is the address of your tails server (must be `https`). - - The `TAILS_FILE_COUNT` environment variable is the size of the tails file that ACA-Py will create per revocation registry. - - The `--revocation` parameter to the `./run-demo` script activates the ACA-Py revocation issuance. - -
- Click here to view screenshot of th revocation registry on the ledger - Ledger -
- -### Revocation Only: Copy the Tails File to GitHub - -Skip this step if you are not using revocation. Jump ahead to [here](#copy-the-faber-invitation). - -As the agent starts up, the tails file is published by the Faber agent itself to a local, non-HTTPS location. Before going further, you need to manually copy the file to github to make it available via https. Follow these steps to do that: - -1. Scroll back in the terminal looking for something like the following in the logs: +You will see something like this for each process: ``` -Revocation Registry ID: EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2 -Revocation Registry Tails File Admin URL: http://127.0.0.1:8021/revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/tails-file -Revocation Registry Tails File URL: https://github.com/ianco/tails-files/raw/master/revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/tails-file -================ -mkdir -p revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/ -curl -X GET "http://127.0.0.1:8021/revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/tails-file" --output revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/tails-file.bin -base64 revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/tails-file.bin >revocation/registry/EiqZU8H9QiFchygR5r3FhJ:4:EiqZU8H9QiFchygR5r3FhJ:3:CL:4420:default:CL_ACCUM:b32580f5-ed8c-4e55-a4e6-8da8c02634b2/tails-file -================ +Forwarding http://abc123.ngrok.io -> http://localhost:8020 +Forwarding https://abc123.ngrok.io -> http://localhost:8020 ``` -There are three terminal commands that you will run in your **local system** terminal session. The commands are the ones in between the "================" markers that you will run in sequence. Read about all three commands before copying/pasting/running them, as the second command may need to be altered slightly and the third skipped. - -1. Go to your local system terminal session and make sure you are in the root folder of the clone of the `tails-file` repo you created. - -2. Run the first one to (`mkdir...`) create the tails-file folder in your local github repo clone. - -2. If the mobile agent you have expects the file to be base64-encoded: - 1. Copy, paste and run the second (`curl...`) and third (`base64...`) commands as is. - -3. If the mobile agent you have expects the file to be as generated by the Indy SDK: - 1. Copy, paste and then edit the second (`curl...`) command and **remove the ".bin" at the end of the command** before running. - 2. Don't execute the third command (`base64...`) at all. - -4. Add, commit and push the file from your local system to GitHub by running the following steps: - -```bash -git add . -git commit -s -m "New tails file" -git push - +``` +Forwarding http://def456.ngrok.io -> http://localhost:6543 +Forwarding https://def456.ngrok.io -> http://localhost:6543 ``` -That's it! You are now serving your tails file on a secure https connection. There's got to be an easier way... -## Copy the Faber Invitation +This creates public urls for ports 8020 and 6543 on your local machine. Keep those 2 processes running as we'll come back to them in a moment. -When the Faber agent starts up it automatically creates an invitation. We will copy the "url" format of the invitation for the next step. Copy all the text between the quotes (do not include the quotes) - the copied text should be a properly formatted URL. -
- Click here to view screenshot - Select Invitation URL -
+### Running Locally in Docker -## Create a QR Code from the Invitation +Then, navigate to [The demo direcory](/demo) and run `PUBLIC_TAILS_URL=https://def456.ngrok.io LEDGER_URL=http://test.bcovrin.vonx.io ADMIN_ENDPOINT=https://abc123.ngrok.io ./run_demo faber --revocation --events` -To get the invitation to the agent, we need to convert the URL into a QR code that your mobile agent will read. Normally, the UI for the Faber agent would do this, but since we are just using the command line, we will use an online QR Code generator for that. +You _must_ use the https urls. And make sure the urls are mapped correctly: `PUBLIC_TAILS_URL` should point to the url mapped to port `6534` and `ADMIN_ENDPOINT` to `8020`. -Open [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) in a new browser window, and: +The `Preparing agent image...` step on the first run takes a bit of time, so while we wait, let's look at the details of the commands. Running Faber is similar to the instructions in the [Aries OpenAPI Demo](./AriesOpenAPIDemo.md) "Play with Docker" section, except: -- Select the "URL" option -- Paste your invitation url into the provided input field +- We are using the BCovrin Test network because that is a network that the mobile agents can be configured to use. +- We are running in "auto" mode, so we will make no manual acknowledgements. +- The revocation related changes: + - The `PUBLIC_TAILS_URL` environment variable is the address of your tails server (must be `https`). + - The `--revocation` parameter to the `./run-demo` script activates the ACA-Py revocation issuance. + - The `ADMIN_ENDPOINT` variable instructs the agent to form its invitation url using this public provided endpoint + +As part of its startup process, the agent will publish a revocation registry to the ledger.
- Click here to view screenshot - Generate QR Code + Click here to view screenshot of the revocation registry on the ledger + Ledger
## Accept the Invitation -On your mobile app, select "SCAN CODE" (or equivalent) and point your camera at the generated QR code. The mobile agent should automatically capture the code and ask you to confirm the connection. Confirm it. +When the Faber agent starts up it automatically creates an invitation and generates a QR code on the screen. On your mobile app, select "SCAN CODE" (or equivalent) and point your camera at the generated QR code. The mobile agent should automatically capture the code and ask you to confirm the connection. Confirm it.
Click here to view screenshot @@ -276,7 +197,7 @@ In the Faber console window, the proof should be received as validated. ## Revoke the Credential and Send Another Proof Request -If you have enabled revocation, you can try revoking the credential pending publication (`faber` options `4` and `5`). For the revocation step, You will need the revocation registry identifier and the credential revocation identifier (with is 1 for the first credential you issues), as the Faber agent logged them to the console at credential issue. +If you have enabled revocation, you can try revoking the credential pending publication (`faber` options `4` and `5`). For the revocation step, You will need the revocation registry identifier and the credential revocation identifier (which is 1 for the first credential you issues), as the Faber agent logged them to the console at credential issue. Once that is done, try sending another proof request and see what happens! Experiment with immediate and pending publication. diff --git a/demo/requirements.txt b/demo/requirements.txt index f2cdf42d3e..2055b24e96 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,3 +1,4 @@ asyncpg~=0.18.0 prompt_toolkit~=2.0.9 git+https://github.com/webpy/webpy.git#egg=web.py +qrcode[pil]~=6.1 \ No newline at end of file diff --git a/demo/run_demo b/demo/run_demo index 7f2f642a3a..e91ccd4855 100755 --- a/demo/run_demo +++ b/demo/run_demo @@ -174,6 +174,9 @@ fi if ! [ -z "$GENESIS_URL" ]; then DOCKER_ENV="${DOCKER_ENV} -e GENESIS_URL=${GENESIS_URL}" fi +if ! [ -z "$ADMIN_ENDPOINT" ]; then + DOCKER_ENV="${DOCKER_ENV} -e ADMIN_ENDPOINT=${ADMIN_ENDPOINT}" +fi if ! [ -z "$EVENTS" ]; then DOCKER_ENV="${DOCKER_ENV} -e EVENTS=1" fi diff --git a/demo/runners/faber.py b/demo/runners/faber.py index af8fb523eb..3c7c69bfe1 100644 --- a/demo/runners/faber.py +++ b/demo/runners/faber.py @@ -6,6 +6,8 @@ import sys import time +from qrcode import QRCode + from aiohttp import ClientError sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # noqa @@ -205,10 +207,12 @@ async def main( connection = await agent.admin_POST("/connections/create-invitation") agent.connection_id = connection["connection_id"] - log_json(connection, label="Invitation response:") - log_msg("*****************") - log_msg(json.dumps(connection["invitation"]), label="Invitation:", color=None) - log_msg("*****************") + + qr = QRCode() + qr.add_data(connection["invitation_url"]) + log_msg("Use the following JSON to accept the invite from another demo agent. Or use the QR code to connect from a mobile agent.") + log_msg(json.dumps(connection["invitation"]), label="Invitation Data:", color=None) + qr.print_ascii(invert=True) log_msg("Waiting for connection...") await agent.detect_connection() diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index 4fca8bc0ee..7b98ee5fbe 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -36,6 +36,8 @@ TRACE_TAG = os.getenv("TRACE_TAG") TRACE_ENABLED = os.getenv("TRACE_ENABLED") +ADMIN_ENDPOINT = os.getenv("ADMIN_ENDPOINT") + DEFAULT_POSTGRES = bool(os.getenv("POSTGRES")) DEFAULT_INTERNAL_HOST = "127.0.0.1" DEFAULT_EXTERNAL_HOST = "localhost" @@ -133,7 +135,9 @@ def __init__( self.trace_tag = TRACE_TAG self.admin_url = f"http://{self.internal_host}:{admin_port}" - if RUN_MODE == "pwd": + if ADMIN_ENDPOINT: + self.endpoint = ADMIN_ENDPOINT + elif RUN_MODE == "pwd": self.endpoint = f"http://{self.external_host}".replace( "{PORT}", str(http_port) ) @@ -220,14 +224,12 @@ async def create_and_publish_revocation_registry( log_msg(f"Revocation Registry ID: {revocation_registry_id}") assert tails_hash == my_tails_hash - # Real app should publish tails file somewhere and update the revocation registry with the URI. - # But for the demo, assume the agent's admin end-points are accessible to the other agents - # Update the revocation registry with the public URL to the tails file - tails_file_admin_url = ( - f"{self.admin_url}/revocation/registry/{revocation_registry_id}/tails-file" - ) tails_file_url = f"{self.public_tails_url}/revocation/registry/{revocation_registry_id}/tails-file" - if RUN_MODE == "pwd": + + if os.getenv("PUBLIC_TAILS_URL"): + tails_file_url = f"{self.public_tails_url}/{revocation_registry_id}" + tails_file_external_url = f"{self.public_tails_url}/{revocation_registry_id}" + elif RUN_MODE == "pwd": tails_file_external_url = f"http://{self.external_host}".replace( "{PORT}", str(self.admin_port) ) @@ -236,6 +238,7 @@ async def create_and_publish_revocation_registry( tails_file_external_url += ( f"/revocation/registry/{revocation_registry_id}/tails-file" ) + revoc_updated_response = await self.admin_PATCH( f"/revocation/registry/{revocation_registry_id}", {"tails_public_uri": tails_file_url}, @@ -243,26 +246,16 @@ async def create_and_publish_revocation_registry( tails_public_uri = revoc_updated_response["result"]["tails_public_uri"] assert tails_public_uri == tails_file_url - # if PUBLIC_TAILS_URL is specified, tell user how to get tails file from agent - if os.getenv("PUBLIC_TAILS_URL"): - log_msg(f"================") - log_msg(f"Revocation Registry Tails File Admin URL: {tails_file_admin_url}") - log_msg(f"Revocation Registry Tails File URL: {tails_public_uri}") - log_msg(f"External host Tails File URL: {tails_file_external_url}") - log_msg(f"================") - log_msg(f"mkdir -p ./revocation/registry/{revocation_registry_id}/") - log_msg( - f'curl -X GET "{tails_file_external_url}" --output ./revocation/registry/{revocation_registry_id}/tails-file.bin' - ) - log_msg( - f"base64 revocation/registry/{revocation_registry_id}/tails-file.bin >revocation/registry/{revocation_registry_id}/tails-file" - ) - log_msg(f"================") - revoc_publish_response = await self.admin_POST( f"/revocation/registry/{revocation_registry_id}/publish" ) + # if PUBLIC_TAILS_URL is specified, upload tails file to tails server + if os.getenv("PUBLIC_TAILS_URL"): + tails_server_hash = await self.admin_PUT_FILE(tails_file, tails_file_url) + assert my_tails_hash == tails_server_hash.decode("utf-8") + log_msg(f"Public tails file URL: {tails_file_url}") + return revoc_publish_response["result"]["revoc_reg_id"] def get_agent_args(self): @@ -545,6 +538,18 @@ async def admin_GET_FILE(self, path, params=None) -> bytes: self.log(f"Error during GET FILE {path}: {str(e)}") raise + async def admin_PUT_FILE(self, file, url, params=None) -> bytes: + try: + params = {k: v for (k, v) in (params or {}).items() if v is not None} + resp = await self.client_session.request( + "PUT", url, params=params, data=file + ) + resp.raise_for_status() + return await resp.read() + except ClientError as e: + self.log(f"Error during PUT FILE {url}: {str(e)}") + raise + async def detect_process(self): async def fetch_status(url: str, timeout: float): text = None