{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Da Vinci CDEX Digital Signature Document Bundle Example\n", "\n", "This is a Jupyter Notebook using Python 3.7 and openSSl to create JSON Web Signature (JWS)(see RFC 7515) and attaching them to a FHIR Bundle.\n", "\n", "*Although self-signed certificates are used for the purpose of these examples, they are not recommended for production systems.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fetch Signed Bundle from FHIR Server\n", "\n", "using AIDBox for now at `https://argopatientlist.aidbox.app/fhir/\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "404\n", "Date = Wed, 16 Feb 2022 07:57:37 GMT\n", "Content-Type = application/fhir+json\n", "Content-Length = 187\n", "Connection = keep-alive\n", "X-Duration = 1\n", "X-Request-Id = 93a28f8679f92f3d3aaf57e7515af7cd\n", "Strict-Transport-Security = max-age=15724800; includeSubDomains\n", "BODY:\n", "{\n", " \"resourceType\": \"OperationOutcome\",\n", " \"text\": {\n", " \"div\": \"GET /fhir/Bundle\\\\foo not found\"\n", " },\n", " \"issue\": [\n", " {\n", " \"severity\": \"error\",\n", " \"code\": \"not-found\",\n", " \"details\": {\n", " \"text\": \"GET /fhir/Bundle\\\\foo not found\"\n", " }\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "from requests import get\n", "from json import dumps\n", "\n", "bundle_id = \"foo\" # insert bundle id here\n", "\n", "url = \"https://argopatientlist.aidbox.app/fhir/Bundle\"\n", "# url = 'http://test.fhir.org/r4/Bundle'\n", "username = \"basic\"\n", "password = \"secret\"\n", "headers = {\"Accept\": \"application/fhir+json\" , \"Content-Type\": \"application/fhir+json\"}\n", "\n", "r = get(f'{url}\\{bundle_id}', auth=(username, password), headers = headers)\n", "print(r.status_code)\n", "for k,v in r.headers.items():\n", " print(f'{k} = {v}')\n", "print(\"BODY:\")\n", "print(dumps(r.json(),indent=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Receiver/Verifier Steps" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1. Remove the `Bundle.signature` element from the Search Bundle resource\n", "- For this example using the python dictionary object from above, but in real life, it would be contained and/or Referenced by TASK over-the-wire. Therefore it would need to be 'decontained' and/or fetched and stored in order to perform these next steps." ] }, { "cell_type": "code", "execution_count": 216, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'type': [{'system': 'urn:iso-astm:E1762-95:2013',\n", " 'code': '1.2.840.10065.1.12.1.5',\n", " 'display': 'Verification Signature'}],\n", " 'when': '2021-10-05T22:42:19-07:00',\n", " 'who': {'display': 'Practitioner/123'},\n", " 'onBehalfOf': {'display': 'Organization/123'},\n", " 'data': 'ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXQwZVNJNklsSlRJaXdpZEhsd0lqb2lTbGRVSWl3aWRYTmxJam9pYzJsbklpd2llRFZqSWpwYklrMUpTVVV6ZWtORFFUQmxaMEYzU1VKQlowbEtRVTlMUmxsMlRYZFNLM2xSVFVFd1IwTlRjVWRUU1dJelJGRkZRa04zVlVGTlNVZE9UVkZ6ZDBOUldVUldVVkZIUlhkS1ZsVjZSVlJOUWtWSFFURlZSVU5CZDB0Uk1rWnpZVmRhZG1OdE5YQlpWRVZUVFVKQlIwRXhWVVZDZDNkS1ZUSkdNV015Um5OaFdGSjJUVkpWZDBWM1dVUldVVkZMUkVGNFNWcFhSbk5rUjJoc1VrZEdNRmxVUlhoR2VrRldRbWRPVmtKQlRVMUVhMVo1WVZkTloxTkhSbWhqZVhkblVrWmFUazFUVlhkSmQxbEtTMjlhU1doMlkwNUJVV3RDUm1oYWJHRkhSbWhqTUVKdldsZEdjMlJIYUd4YVIwWXdXVlJGZFdJelNtNU5RalJZUkZSSmVFMVVRWGxPZWtVelRrUkpkMDVHYjFoRVZFbDVUVlJCZVUxcVJUTk9SRWwzVGtadmQyZFpNSGhEZWtGS1FtZE9Wa0pCV1ZSQmJGWlVUVkpOZDBWUldVUldVVkZKUkVGd1JGbFhlSEJhYlRsNVltMXNhRTFTU1hkRlFWbEVWbEZSU0VSQmJGUlpXRlo2V1ZkNGNHUkhPSGhHVkVGVVFtZE9Wa0pCYjAxRVJXaHNXVmQ0TUdGSFZrVlpXRkpvVFZSRldFMUNWVWRCTVZWRlFYZDNUMUpZU25CWmVVSkpXVmRHZWt4RFFrVldhekI0U2xSQmFrSm5hM0ZvYTJsSE9YY3dRa05SUlZkR2JWWnZXVmRHZWxGSGFHeFpWM2d3WVVkV2ExbFlVbWhOVXpWMlkyMWpkMmRuUjJsTlFUQkhRMU54UjFOSllqTkVVVVZDUVZGVlFVRTBTVUpxZDBGM1oyZEhTMEZ2U1VKblVVUndTMk5UYTI5QlRUWnpWekl4SzNaWFZHVkpWazlIZURFd1RWZGhjMUY1TjFaSWFXUTJlbmx4V0VGQ1RTdDZibVpDYmxobGJubFZNR294UmxSMlVHMVNaazlFYjA5RVdGWjFVRlYzUkc5dGFFTklhQ3RpWTJ4WE9VdE5NbTgxTmpOamVGSkxSWFpDYm1GSWNuTnFkelY1VG0xNFR6Vlpha1ZTWW1oMFNHUlJaWEZyZEdSM00xWlpSVkpTT1VodmVFeFBNMFpyYzNwU01qa3lTRlJDTkhoWE0zbFhiRll6WjFSclRWRnZlbEJUWTBwTFNETmlSemhRY1hFMlFWbFFTamRETkZsQ1NXeFZVMlJDVFZac00zRnVaVVZtWnpkbWRYaHBSbVpZYjJaa1ZGWnROM0pOYVdsSE4xZzVlalF6VUdacGJIRmhaV2x6Wm0xMFVuaEJiRkozUlU1WWNrZ3pUM1pQUkZCNVREQnlWRzVIT0VOellrRllXVlpKVFcxa1pFaGxORnBHT1hCc2FEazFjMm8wY0UxVWFFeDBZMHBZTDI4NVdFaE1hbWczUlcxYWVXZEtTRmRGVVhFMFVIZEdkMXBrYldKalptaERiVTl5T0RoSU9FSmlWWEoxTHpkV05ucGljMGN4VGpGRFYyeHVaR3hpVm5wdVRDc3pTVTFQY2pocldHRklZMkZ1Y1daamEyZEdWalJGY201bWFrWktjVEZQU1dGQmJYTk5hamcxZUUxcmFubFlUSGxqVEV3dmRUVnVNbTgyUW1jNU15OVZVbVp4ZFU5dlUwbEhUME5TTWpWRVlWcDZjSGN5YXpOek4yOUZPV1JOZDBWWFdIUm1XR2RaZEdneVlteHFlVFYwUmtnd1IycHdUMnQ0TURkcU4xcFVOVWh1ZUc1c2MwTkJkMFZCUVdGT1FVMUVOSGRFUVZsRVZsSXdWRUpCVlhkQmQwVkNMM3BCVEVKblRsWklVVGhGUWtGTlEwSmxRWGRKVVZsRVZsSXdVa0pDYjNkSFNVbFhaRE5rTTB4dGFHeFpWM2d3WVVkV2ExbFlVbWhoVnpWcVRHMU9kbUpVUVU1Q1oydHhhR3RwUnpsM01FSkJVWE5HUVVGUFEwRlpSVUZEZFUxVlRuRTVZWGtySzJVMVdVTTNVVVpQT1RSeVpucDRSMUZ1UmpOSGEyeGFUa0ZZYlVseU4xQldSMlJwUjFreVIxUjRMemxTZEVoRGQxUkxlbXRNSzNsMlMyOXFaVm81WkZaTE9IZHlSMVpwVW10UEwycFZlVm9yUzJOWFVtOXJWV3B6TlRsdVkwcEhVazFUVTFKNGRHVkRVWFZxZERSb1pqSXJMM0ZXSzJZeWMwMVJkRVZ5ZDFCRk16QjJZbkZTV1ZWT1RrNUNWa1ZSY0dGUmVDOWhZMHBFVlhZNWRqZHpha2hwU2tSeFdIZFJLM0o2YWprMWFVaEJTV0ZsUlVoeFJpOU5jekl5Y0RKaVpWcDFjWFpKVVV0bFRXd3JjM1pXY1VoMGFYVjZWMjVHTkZVMlZrbHRjR3R5TkdKSWJEZGxaMVk1U0Rac05sUXlVMDFyYWpaeFJGVTFaVGxPWnpCYWJFeFVkRzl6YzJoQ1RHMXZjRVkzWlRkSWVYSlVSVUZ0Wms5UVMxRmxNRVZuT1VVeWRYSjZlSEZDZFVjMU5HczFNRWN5U2pCR2FWQnpVVXBCYUVacFRrZDNVMmMwVXpOSWVWWkVSemQxWlV0a01FdzVNM2RMVDA1UFdVZDJUVXRwZWtOSVFpdHdTM1pGVFVwdldqaDVPWFZwUWl0SVJsaGpjRGxTWVVweFNqazNTSEJhVkVWMksyeHBRM0F5VUZOWWVtTkxNSEl5TlZOamVXcEdObVJNYjNWTE1sTkNNekI0UVhaS09IUkZOVGcwSzJweFVUWkRSMlZqVlRsWWFuWnNRV3AxU21SRGNrUmxWbEJ6YW10dU4wOVFXRWNyT0ZoYVZUZDZjVWhhVG0xWVdERlpXVFJJTlhKblJVbzBPR3h5VlhKUU0wazRVaUpkZlEuLmQ0MjByMFk1cGktLU9rSW1DaWhselA0XzhQd1BaaUVRcjRQeTJyZER0QXdubF9OYUU1SDFucXp2bm1qU00zSkhuaEhRakhzeDBZOWxjWEhpc2ZUbUp5QmlNYmhIQ28ycGRHeUx6RTZrbTdNYlktSTYyOXlLRy1ENXNLaFFldklYblRlZ1dHN3h6WTNDQjlHUTMzSUFEcU1EZkctdkg3N21nR2xjQk05WFRJX1liRzZGZUZqd0Z3TGcyenYxM0JWNzI3d3U2RWpDNnhPSHRiQ3NuS1NxUnZheFNZWVIwWWgyNG9jZmJuaVFiWHhEVlNndFFwNWlWUjdQUkdvX29JNEFscHozQVBUcmNaYUVNdXI5VWhoNm81MEZYbkUzZTlpMkM5TThwTkhyY3d4SjdWV2d1UGJ3UGN0dFd0ZG9RYXBKRWQwZnR1WEVwWnhzZ2t3aWgzSGZQQXQ3Vm84a1ZsUkRIM0lrQ0FBNVg2cUtlYzFIU2hYRFp1QklqMFNWVUJHSDFsMlRPb0VCd29nWGhlTDkwbHVqejRBUnpabHdfSzNrQk1tUk10dHVucDdjLWpZYmdubVRpcE1MQW4weWhSOUFsSmdUMVV4Z2FpTE5adTJtekZuMHZSTkU2OU95cm5Rc0Z3THhWQlhCcUJ2NzRyWmNPbGQ5OVJydENEVEdHemlR'}" ] }, "execution_count": 216, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_bundle = r.json()\n", "\n", "def pop_element(resource, element):\n", " try:\n", " my_element = resource.pop(element) # remove element\n", " return my_element \n", " except KeyError:\n", " pass\n", " \n", "recd_signature = pop_element(my_bundle, 'signature')\n", "recd_signature" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. Canonicalize the bundle using IETF JSON Canonicalization Scheme (JCS):\n", "\n", "- Remove the id and meta elements if present before canonicalization" ] }, { "cell_type": "code", "execution_count": 217, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(b'{\"entry\":[{\"fullUrl\":\"http://fhir.healthintersections.com.au/open/Composition/180f219f-97a8-486d-99d9-ed631fe4fc57\",\"resource\":{\"author\":[{\"display\":\"Doctor Dave\",\"reference\":\"Practitioner/example\"}],\"confidentiality\":\"N\",\"date\":\"2013-02-01T12:30:02Z\",\"encounter\":{\"reference\":\"http://fhir.healthintersections.com.au/open/Encounter/doc-example\"},\"id\":\"180f219f-97a8-486d-99d9-ed631fe4fc57\",\"meta\":{\"lastUpdated\":\"2013-05-28T22:12:21Z\"},\"resourceType\":\"Composition\",\"section\":[{\"code\":{\"coding\":[{\"code\":\"29299-5\",\"display\":\"Reason for visit Narrative\",\"system\":\"http://loinc.org\"}]},\"entry\":[{\"reference\":\"urn:uuid:541a72a8-df75-4484-ac89-ac4923f03b81\"}],\"text\":{\"div\":\"
| Details | \\\\n\\\\n\\\\n\\\\n |
| Acute Asthmatic attack. Was wheezing for days prior to admission. | \\\\n\\\\n\\\\n\\\\n |
| Medication | \\\\n\\\\nLast Change | \\\\n\\\\nLast ChangeReason | \\\\n\\\\n
| Theophylline 200mg BD after meals | \\\\n\\\\ncontinued | \\\\n\\\\n|
| Ventolin Inhaler | \\\\n\\\\nstopped | \\\\n\\\\nGetting side effect of tremor | \\\\n\\\\n
| Allergen | \\\\n\\\\nReaction | \\\\n\\\\n
| Doxycycline | \\\\n\\\\nHives | \\\\n\\\\n
Generated Narrative with Details
id: 180f219f-97a8-486d-99d9-ed631fe4fc57
meta:
status: final
type: Discharge Summary from Responsible Clinician (Details : {LOINC code \\'28655-9\\' = \\'Physician attending Discharge summary)
encounter: http://fhir.healthintersections.com.au/open/Encounter/doc-example
date: 01/02/2013 12:30:02 PM
author: Doctor Dave
title: Discharge Summary
confidentiality: N
Dr Adam Careful
\\\\n\\\\nTheophylline 200mg twice a day
\\\\n\\\\nVentolin inhaler discontinued
\\\\n\\\\n