Stream: smart/health-cards
Topic: SHC Pre-event Discussion
Michael Turman (Apr 09 2021 at 18:01):
https://confluence.hl7.org/display/CAR/Track+Page+for+the+SMART+Health+Card
Abbie Watson (Apr 09 2021 at 23:01):
Hello SmartHealthCards!
Abbie Watson (Apr 10 2021 at 03:09):
So.... I managed to generate a QR code with a FHIR Immunization record on it for Candace Salinas. Thought it might be a nice way to to get people conversing on this upcoming track, and to kick things off. Can anybody verify with a smartphone camera that they can get the FHIR resource? Thanks in advance!
Symptomatic-VaccineWallet-QrCode-CandaceSalinas.png
Josh Mandel (Apr 10 2021 at 08:05):
Excellent!!
https://smarthealth.cards/#what-testing-tools-are-available-to-validate-smart-health-cards-implementations includes a link to the validation SDK (nodejs CLI, will take QRs as input and run them through their paces) and a few other helpful resources.
I also have a rough web based QR scanner + card validator at https://joshuamandel.com/health-card-scanner-demo (source at https://github.com/jmandel/health-card-scanner-demo) that folks are welcome to try if anyone wants to give this a shot from their own device. (If doesn't give useful feedback about any problems, but it'll print a fhir bundle to the screen if things work. In the case of the QR above, it doesn't work; see notes below for why.)
Josh Mandel (Apr 10 2021 at 08:11):
Validator gives you a good start on issues to address:
$ node . -p ~/Downloads/Symptomatic-VaccineWallet-QrCode-CandaceSalinas.png -t qr -l debug
SMART Health Card Validation SDK v0.4.3
QR image
|
├─ Debug :
| Symptomatic-VaccineWallet-QrCode-CandaceSalinas.png = {"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"id":"3","encounter":{"reference":"Encounter/129837645"},"date":"2017-10-18","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"Influenza","coding":[{"system":"CVX","code":"135"}]},"resourceType":"Immunization","_id":"J4TjYWRYL5yqWqfor","_document":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"id":"3","encounter":{"reference":"Encounter/129837645"},"date":"2017-10-18","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"Influenza","coding":[{"system":"CVX","code":"135"}]},"resourceType":"Immunization","_id":"J4TjYWRYL5yqWqfor"}}
|
├─ Info :
| Symptomatic-VaccineWallet-QrCode-CandaceSalinas.png decoded
|
QR numeric
|
└─ Error :
Invalid numeric QR header: expected shc:/[0-9]/[0-9]/[0-9]+
... but briefly this bundle needs to be:
- updated with resources that match the Vaccination data profiles,
- packaged to follow https://smarthealth.cards/#health-cards-are-small, and
- encoded to follow https://smarthealth.cards/#creating-a-qr-code-or-a-set-of-qr-codes-from-a-health-card-jws
Josh Mandel (Apr 10 2021 at 08:14):
(While you've got fresh eyes @Abigail Watson , would love feedback or ideas on how to highlight all of this better for new developers.)
Abbie Watson (Apr 10 2021 at 14:27):
Well, as far as feedback and ideas, there are three immediate things that come to mind:
First, I think there's going to be unsigned FHIR resources in the ecosystem. That's just going to be a pragmatic reality. So, I think we need discussion around what implementors should do in the unencrypted usecase... which is precisely why I started with a FHIR Immunization resource rather than starting with the FHIR Bundle structure defined in the SMART HealthCard spec. Validator is good. But as a developer, I need to know how to handle this exception case (which could be very common).
(And frankly... I have to ask the devil's advocate question: do we really know/believe that man-in-the-middle and forgery attacks are going to be all that common? Or that the harm of such would materially impact users? Or would alter the course of this or any other pandemic? Security is good. But from a functionality perspective, sharing raw FHIR Immunization resources is... sufficient. It streamlines a touchpoint where users would want a human readable format anyhow. It gets the job done. So, I'm sitting here with a proof-of-concept of a working screening app that could be used in any number of spaces in the real world. And I've got this pragmatics question of 'what does the extra cryptography infrastructure really get me'?)
Devil's advocate discussion aside, all of the above leads to my second item, which is I think we need a value set to define validation status/result. Something that can trigger a badge or icon or something in the user interface after the QR code is scanned. I'm thinking something along the lines of:
- No known immunization/vaccination
- Vaccinated
- Multiple vaccinations
- Known prior COVID hospitalization
- Known prior convalescent plasma transfusion
- Known prior positive COVID test
Getting the validation status right is going to be both important, and possibly thorny. I think if people were to do a survey of Immunization Systems, they'd find the majority of them are state/government sponsored, and are focused on receiving jabs in the arm, rather than modeling human physiology and whether a person has immunity to a disease. And from a civil liberties perspective, the spec could reduce freedom of travel and prevent people from getting healthcare or jobs or seeing family if it's a binary vaccinated/not-vaccinated result.
The catch, however, is I'm not sure my proposed language covers non-COVID use-cases or is scalable for immunizations in general. It's language needs some massaging; possibly from vocab and modeling facilitators.
Third, for the spec to succeed, I think it needs to be able to hand out a temporary 3day pass after a negative PCR test, a 3mo+ pass after a blood transfusion or COVID hospitalization, and a 1yr pass after a vaccination. Or whatever lengths of time research studies suggest. But to have that expansive multi-modal approach, we have to also dig into lab results and Observation resource. Happily, the Bundle approach is already set up to support that. But we may want to think about a supplemental COVID addendum of some sort, with SNOMED/LOINC codes that qualify for or confer immunity.
Josh Mandel (Apr 10 2021 at 18:01):
I think there's going to be unsigned FHIR resources in the ecosystem. That's just going to be a pragmatic reality.
For sure: there's lots of data out there in the world; most of it isn't FHIR but some is; among FHIR data most of it isn't signed. SMART Health Cards is specifically about sharing verifiable clinical data bound to an individual's identity. We've left unsigned data out of scope here. That said, anyone can sign a Health Card, so you can always create signed data; it's up to a verifier to decide if they want to trust your signature.
Josh Mandel (Apr 10 2021 at 18:03):
I think we need a value set to define validation status/result. Something that can trigger a badge or icon or something in the user interface after the QR code is scanned.
We have a couple of FAQs that address this:
- https://smarthealth.cards/#how-can-we-share-conclusions-like-a-safe-to-fly-pass-instead-of-sharing-clinical-results
- https://smarthealth.cards/#which-clinical-data-should-be-considered-in-decision-making
The short answer is that the SMART Health Cards spec isn't trying to frame policy, or describe how you act on data. It's focused on the verifiable clinical data. There are a number of efforts focused on "Pass generation" which might be a better fit for this discussion.
Michael Turman (Apr 10 2021 at 18:59):
@Abigail Watson I was able to get the FHIR resource after downloading a QR scanning app. My native camera app (Android v11 - Pixel 4) was not able to parse the code.
Abbie Watson (Apr 10 2021 at 19:11):
Josh Mandel said:
That said, anyone can sign a Health Card, so you can always create signed data; it's up to a verifier to decide if they want to trust your signature.
This right here is important and would suffice for the discussion, as people are going to have questions about this. I came to the same conclusion, that our app/business would probably need to act as a signing authority and notary of sorts. Doable. But worth spelling out, maybe giving some resources, and considerations of what that entails.
Paul Denning (Apr 11 2021 at 23:15):
https://confluence.hl7.org/x/SwVxBg PHWG page for SHC VCI IG, FYI
Josh Mandel (Apr 12 2021 at 00:01):
Public Health Workgroup page for "SMART Health Cards: Vaccination Credential Implementation Guide", for your information
(de-acronymed, save for SMART. If you want to be pedantic: Substitutable Medical Applications, Reusable Technologies ;-)
Abbie Watson (Apr 12 2021 at 00:55):
(deleted)
Abbie Watson (Apr 12 2021 at 01:42):
Second step... put the FHIR Immunization into a SMART Healthcard Bundle. Still working on signing it, but I think the data shaping is done.
Symptomatic-VaccinePassport-SmartBundle-Influenza-NotSigned.png
Abbie Watson (Apr 12 2021 at 06:08):
Okay, so I'm a little confused. I managed to generate the public/private keys, created the .well-known/jwks.json
file, created the JWT response with the fhirBundle
, created a payload, signed it with the private key, and generated a token.
But what am I suppose to do with the token now? I'm missing something....
Abbie Watson (Apr 12 2021 at 06:13):
Ehhh.... this doesn't seem right.
Screen-Shot-2021-04-12-at-1.12.39-AM.png
Steve Hall (Apr 12 2021 at 13:33):
@Abigail Watson have you looked into the numeric encoding of the bundle yet? https://smarthealth.cards/#every-health-card-can-be-embedded-in-a-qr-code
Abbie Watson (Apr 12 2021 at 13:43):
Honestly, trying to avoid it if possible, as the component APIs that I tend to work with are much higher level. The SMART spec is sort of lost in the weeds of technical implementation, compared to where the open-source Javascript community APIs are at.
https://www.npmjs.com/package/qrcode.react
Josh Mandel (Apr 12 2021 at 13:43):
Once you have a JWS there are a few things you can do:
- For debugging, feed the JWS directly into the validator sdk, which accepts inputs of this type in a plain text file
- For printing or displaying a QR, encode the JWS following guidance that Steve links to above
- For file-based sharing, package the JWS (and optionally additional Health Card JWSs) in a
.smart-healtg-card
JSON file (for example, to make this available for a patient to download into a wallet of their choice)
Josh Mandel (Apr 12 2021 at 13:45):
Our specification is describing the low level details to ensure interoperability, and specific libraries are going to offer higher level wrappers around this functionality. You will need to understand enough of the underlying details in order to use your libraries correctly. If you want guidance for using that specific JavaScript library, you are in luck because it is the same one we use in our own example generator :-)
Abbie Watson (Apr 12 2021 at 13:46):
Mmmm... right now the focus is getting QR code’s working. Seems like there’s a lack of context in the QR workflow, where users might not even know which app they’re communicating with. Seems like the issuer’s iss
field should be passed around with the signed token, so they know how to look up the .well-known/jswk.json file, no?
Josh Mandel (Apr 12 2021 at 13:46):
https://smarthealth.cards/#what-testing-tools-are-available-to-validate-smart-health-cards-implementations includes links to our example code and also to a Jupiter notebook showing how this library can be used. The notebook is probably a good starting point.
Abbie Watson (Apr 12 2021 at 13:48):
I’ll publish the signing workflow in just a bit and provide a workflow audit. Have a couple of early morning meetings, should have it by lunchtime.
Josh Mandel (Apr 12 2021 at 13:48):
The relevant context for applications processing these QR values is in the prefix -- this shc:/
URI scheme (together with index values like shc:/1/2/
for a JWS that's too large for a single QR) tells you how to route these things to the appropriate app
Abbie Watson (Apr 12 2021 at 15:50):
Okay, have the SMART Health Cards Validation SDK running. :sunglasses:
Abbie Watson (Apr 12 2021 at 18:04):
Okay, just to confirm (because the documentation is pretty technical), I go to https://mkjwk.org/ and generate the keys like so:
Screen-Shot-2021-04-12-at-1.01.09-PM.png
And I want the center-right "Public and Private Keypair Set" to be hosted publicly on {$baseUrl}/.well-known/jwsk.json
, and the Private Key (X.509 PEM Format) held privately on the server for when we do the signing and token generation.
Abbie Watson (Apr 12 2021 at 18:05):
(I'm going to walk through this publicly, and re-generate keys later.)
Abbie Watson (Apr 12 2021 at 18:06):
The d
portion is the private key, ah. Got it, so it gets removed in the .well-known/jwsk.json
Josh Mandel (Apr 12 2021 at 18:10):
I haven't used this tool before and don't know that I'd recommend it as a best practice vs using a dedicated script like this one -- but that said, it looks like you've chosen the right key setup options.
- Obviously you'd only host the public key (center-right box, not center-top) at
/.well-known/jwks.json
(I'm sure that was a typo on your part and now I see you corrected it in your follow-up, but have to call it out ;-)) - Re: private key, yes you'd keep it on your server. Up to you if you'd want to save it in PEM format vs just keeping it in a JWKS
Abbie Watson (Apr 13 2021 at 13:44):
Okay, I think I may have figured it out. Elliptic cryptography is elliptic, with multiple keys. So the JWT token can be decoded (not verified) without the signing pair's public key. I think.
Can anybody verify that the following works for them? I'm working on getting validator tool results next.
Symptomatic-VaccinePassport-SmartBundle-Coronavirus-Signed.png
Josh Mandel (Apr 13 2021 at 13:50):
This is true of all JWSs, irrespective of the signature algorithm: you can read the data payload without a key; you only need a key when you want to verify the signature over the payload.
Josh Mandel (Apr 13 2021 at 13:51):
At a glance, the QR in your screenshot is too dense; the specification says that any JWS that's too long to fit in a V22 symbol (roughly: JWS longer than 1200 characters) needs to be split up across multiple QRs. But basic vaccination records with data minimization practices in place aren't generally that big...
Matt Printz (Apr 13 2021 at 14:26):
Are there existing patients in the open reference server that we can use for basic testing, or do we need to register for the Patient Access server?
Michael Turman (Apr 13 2021 at 14:49):
@Matt Printz If you are referring to the Cerner reference server, the health card is mocked (hard coded) to return the same patient regardless of patientId passed in the request. Once we move beyond our reference implementation, I would expect our sandbox server to have more robust patient scenarios.
Matt Printz (Apr 13 2021 at 14:51):
@Michael Turman I was, and got it. Sounds good. Thanks!
Steve Hall (Apr 13 2021 at 15:59):
@Abigail Watson The payload of the QR code requires the 'shc:/' prefix, and as @Josh Mandel mentioned, your data is too large for the QR format (this QR has 1,972 characters). The other thing you'll want to look into is the numeric encoding of the data in order to meet the SMART HealthCard implementation guidelines.
Josh Mandel (Apr 13 2021 at 16:07):
Abigail Watson said:
Can anybody verify that the following works for them? I'm working on getting validator tool results next.
Manually scanning that QR I see the contents are:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3ZhY2NpbmUtcGFzc3BvcnQuc3ltcHRvbWF0aWMuaW8iLCJuYmYiOjEwLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiaHR0cHM6Ly9zbWFydGhlYWx0aC5jYXJkcyNoZWFsdGgtY2FyZCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaGlyVmVyc2lvbiI6IjQuMC4xIiwiZmhpckJ1bmRsZSI6eyJyZXNvdXJjZVR5cGUiOiJCdW5kbGUiLCJ0eXBlIjoiY29sbGVjdGlvbiIsImVudHJ5IjpbeyJmdWxsVXJsIjoiSW1tdW5pemF0aW9uL2Nvcm9udmlydXMiLCJyZXNvdXJjZSI6eyJzdGF0dXMiOiJjb21wbGV0ZWQiLCJ3YXNOb3RHaXZlbiI6ZmFsc2UsInBhdGllbnQiOnsiZGlzcGxheSI6IkNhbmRhY2UgU2FsaW5hcyIsInJlZmVyZW5jZSI6IlBhdGllbnQvMTAwIn0sImlkIjoiaW1tdW5pemF0aW9uLW1tciIsImVuY291bnRlciI6eyJyZWZlcmVuY2UiOiJFbmNvdW50ZXIvMTI5ODM3NjQ1In0sImRhdGUiOiIyMDIxLTAzLTA1IiwicmVxdWVzdGVyIjp7ImRpc3BsYXkiOiJBbHRpY2sgS2VsbHkiLCJyZWZlcmVuY2UiOiJQcmFjdGl0aW9uZXIvOCJ9LCJyZXBvcnRlZCI6ZmFsc2UsInZhY2NpbmVDb2RlIjp7InRleHQiOiJTQVJTLUNPVi0yIChDT1ZJRC0xOSkgdmFjY2luZSwgbVJOQSwgc3Bpa2UgcHJvdGVpbiwgTE5QLCBwcmVzZXJ2YXRpdmUgZnJlZSwgMzAgbWNnLzAuM21MIGRvc2UiLCJjb2RpbmciOlt7InN5c3RlbSI6IkNWWCIsImNvZGUiOiIyMDgifV19LCJyZXNvdXJjZVR5cGUiOiJJbW11bml6YXRpb24iLCJfaWQiOiJUd3c5a20yWVo0bURTbmVCVyIsIl9kb2N1bWVudCI6eyJzdGF0dXMiOiJjb21wbGV0ZWQiLCJ3YXNOb3RHaXZlbiI6ZmFsc2UsInBhdGllbnQiOnsiZGlzcGxheSI6IkNhbmRhY2UgU2FsaW5hcyIsInJlZmVyZW5jZSI6IlBhdGllbnQvMTAwIn0sImlkIjoiaW1tdW5pemF0aW9uLW1tciIsImVuY291bnRlciI6eyJyZWZlcmVuY2UiOiJFbmNvdW50ZXIvMTI5ODM3NjQ1In0sImRhdGUiOiIyMDIxLTAzLTA1IiwicmVxdWVzdGVyIjp7ImRpc3BsYXkiOiJBbHRpY2sgS2VsbHkiLCJyZWZlcmVuY2UiOiJQcmFjdGl0aW9uZXIvOCJ9LCJyZXBvcnRlZCI6ZmFsc2UsInZhY2NpbmVDb2RlIjp7InRleHQiOiJTQVJTLUNPVi0yIChDT1ZJRC0xOSkgdmFjY2luZSwgbVJOQSwgc3Bpa2UgcHJvdGVpbiwgTE5QLCBwcmVzZXJ2YXRpdmUgZnJlZSwgMzAgbWNnLzAuM21MIGRvc2UiLCJjb2RpbmciOlt7InN5c3RlbSI6IkNWWCIsImNvZGUiOiIyMDgifV19LCJyZXNvdXJjZVR5cGUiOiJJbW11bml6YXRpb24iLCJfaWQiOiJUd3c5a20yWVo0bURTbmVCVyJ9fX1dfX19LCJpYXQiOjE2MTgyOTAxODl9.xxNfq0CTDji5icgGE9dhuY4_LVRJcwIb4hWvdkvewiJqicj2N39DiVpBiIaR_ClEYJJ77BRxADMOSWopsF0U7A
There are a few items to address here:
-
This needs to be encoded according to the rules at https://smarthealth.cards/#creating-a-qr-code-or-a-set-of-qr-codes-from-a-health-card-jws rather than represented as a "raw" JWS. So the QR contents should look like https://smarthealth.cards/examples/example-00-f-qr-code-numeric-value-0.txt rather than like https://smarthealth.cards/examples/example-00-d-jws.txt (see https://smarthealth.cards/examples/ for context)
-
nbf
claim should be an epoch time in seconds, not10
(the value you have foriat
can be re-used, or better yet you can just renameiat
so you only havenbf
) type
array should include relevant subtypes from https://smarthealth.cards/vocabulary/ (#covid19
and#immunization
)- Bundle.entry.fullUrls (and Reference.values) should follow the guidance at https://smarthealth.cards/#health-cards-are-small (e.g.,
resource:0
instead ofImmunization/coronvirus
). - Data profiles should match the DM (data minimization) guidance from http://build.fhir.org/ig/dvci/vaccine-credential-ig/branches/main/ (e.g., remove _id, _document, wasNotGiven, patient.display, encounter, requester, reporter, vaccineCode.text; use a correct CVX system URL;
- Looks like there are two copies of the same Immunization, one of which has properties in
_document
, which isn't a valid FHIR JSON resource - You need a Patient resource (linked from Immunization) to convey name and DOB
Abbie Watson (Apr 13 2021 at 16:10):
Yeah, I've been tooling around with zlib's inflate/deflate this morning, removed the internal _document
field, and have it down to around ~1068 characters now. And I think I've figured out how to put in the shc
prefix in.
Abbie Watson (Apr 13 2021 at 16:14):
Although if I'm having these questions, other people are going to have them also. I get the desire to compress and optimize and all, but it all begs the questions 'why not just use a larger QR code?' and 'what are we gaining from all this extra engineering?'.
Josh Mandel (Apr 13 2021 at 16:20):
Scanning large QR codes requires higher quality cameras and a steady hand; if anything, I'm worried that V22 codes already a bit too high for practical in real-world use, but we're basing the current recommendations on our initial round of testing. This is a place where the UX will fall down if it's not easy to scan codes, and it's worth doing some extra work to support this.
Josh Mandel (Apr 13 2021 at 16:22):
(That said, it's a big spectrum, and our design choices are still weighted heavily towards "make it easy for developers by re-using FHIR structures"; alternatives define custom schemas for this purpose, or use techniques like CBOR-LD or Protobuf encoding, which come with their own stack of dependencies.)
Josh Mandel (Apr 13 2021 at 16:23):
(e.g., https://github.com/Path-Check/paper-cred uses a set of custom data models and a set of narrow-purpose QR codes; it represents a very different point in the design space.)
Abbie Watson (Apr 13 2021 at 16:40):
I'd maybe add language to that effect at the beginning of the spec.
Where the current recommendations have landed seems a bit arbitrary. On the one hand, equity to access and running on lower model phones is great. On the other hand, any sort of screening activity has already implicitly conceded a certain amount of inequality... be it circumstantial, economic, or otherwise. So, I get all the reasoning that led to where it currently is; but I got to wonder about premature optimization and all that, and whether those assumptions all hold for implementors looking at the spec.
Ex: Removing reference display
fields, but requiring the Patient resource for example. Okay, fine. Seems a bit overengineered. The display field was originally intended for compactness so the resource references wouldn't need to be resolved for a single display field. And why the need for the birthdate in the QR code?
I suppose I came into the connectathon hoping to be able to stamp/encode non-Bundle resource singletons. Just the FHIR Immunization record, as it were. Not the end of the world, but different than what I was expecting.
Josh Mandel (Apr 13 2021 at 16:57):
The display field was originally intended for compactness so the resource references wouldn't need to be resolved for a single display field
This flows from requirements though: name alone doesn't provide enough context for identity binding, and you can't cram a birthdate into a display :-)
Josh Mandel (Apr 13 2021 at 16:58):
I suppose I came into the connectathon hoping to be able to stamp/encode non-Bundle resource singletons. Just the FHIR Immunization record, as it were. Not the end of the world, but different than what I was expecting.
Yeah, that's fair!
Abbie Watson (Apr 13 2021 at 17:28):
Josh Mandel said:
This flows from requirements though: name alone doesn't provide enough context for identity binding, and you can't cram a birthdate into a display :-)
Putting on my Patient Empowerment Co-Chair hat for a moment - who exactly defined the requirements? Microsoft|Harvard|HL7 Public Health? And were patient advocates participating?
The identity binding requirement seems like it would certainly be needed if we were only discussing state/government sanctioned Immunization Registries and international borders. But was any consideration given to the travel ecosystem? Event planning coordination? Campus security? Right to association?
I don't need to show ID or proof of age to submit receipts. When I park my car in a garage and am issued a parking claim, my right to travel is temporarily encumbered by my obligation to show receipt and proof of payment before I'm allowed to remove my vehicle and begin traveling again. It's a common form of travel restriction used in parking garages across the country, and no ID proofing is required.
Who decided that identity proofing is a requirement for the SMART HealthCard, and not an optional addition/upgrade?
:takes hat off:
Josh Mandel (Apr 13 2021 at 17:51):
The requirement for offering identity binding is core to the concept of verifiable data here; records need to represent data about someone. More formally, the details about what level of identity binding, and what's optional vs required vs prohibited... those decisions don't live in the SMART Health Cards Framework IG, but rather in use case-specific profiles, the first of which is http://build.fhir.org/ig/dvci/vaccine-credential-ig/branches/main/ ; this is just beginning to move through the HL7 process, sponsored by the Public Health Workgroup, and I'd certainly suggest that the Patient Empowerment WG might want to register as an interested party.
Josh Mandel (Apr 13 2021 at 17:53):
(Structurally though, knowing that use-case-specific profiles will set these kinds of rules is what tells us we need to support referencing a Patient in the core framework. I don't mean to over-emphasize the specific rules, but rather just to highlight the requirements that emerge simply by factoring the rules into use-case-specific profiles.)
Abbie Watson (Apr 13 2021 at 20:01):
Okay, I'm making some progress here, but want to verify that I have the algorithm correct. Am I missing a base64 conversion in there?
// step 0 - build our verifiable credential with bundle
VerifiableCredential{Bundle{Immunization}}
// step 1 - delete unnecessary fields
delete_fields()
//step 2 - stringify
stringify()
// step 3 - trim
trim()
// step 4 - deflate
deflate()
step 5 - sign
sign()
step 6 - convert to numeric mode
numeric(signature)
step 7 - write to QR component
"shc:/" + numeric_signature
Alan Viars (Apr 13 2021 at 20:13):
Here is my preview. I'll be working on the BlueButton CARIN activity too. Screen-Shot-2021-04-13-at-4.06.39-PM.png ads well but wanted to throw this out as test implementation.
Josh Mandel (Apr 13 2021 at 20:17):
This looks right! It'd be good to add some pseudo-code to highlight inputs to each step, just to be sure -- so take this as a friendly amendment :)
// step 0 - build our verifiable credential with bundle
let fhirData = VerifiableCredential{Bundle{Immunization}}
// step 1 - delete unnecessary fields
let fhirDataMinimized = delete_fields()
// step 2
let vcPayloadJson = insert_fhir_data_into_vc_scaffold(fhirDataMinimized)
// step 3
let vcPayloadString = trim(stringify(vcPayloadJson))
// step 4 - deflate
let vcPayload = deflate(vcPayloadString)
// step 5 - sign -- note that this step will result in a JWS where the header and payload are both b64urlencoded
let jws = jwsSign(vcPayload)
// step 6 - convert to numeric mode
let qrJwsContents = numeric(jws)
// step 7 - create QR Symbol
let qrSymbol = generateQrFromSegments([{
mode: bytes,
value: `shc:/`
}, {
mode: numeric,
value: qrJwsContents
}])
.... though https://github.com/dvci/health-cards-walkthrough/blob/main/SMART%20Health%20Cards.ipynb provides a better overview IMO!
Alan Viars (Apr 13 2021 at 20:18):
At this early stage I'll still need to create your SHC manually in the admin. A user account will be needed. I am creating an Org called CARIN Connect-a-thon for this purpose. Create a member account there and I'll approve your account and then create your SHC. This is still a work in progress. The URL for the account creation is here: https://id.verifymyidentity.com/accounts/create-member-account/CARIN-Connect-A-thon/
Josh Mandel (Apr 13 2021 at 20:21):
Here is my preview. I'll be working on the BlueButton CARIN activity too. Screen-Shot-2021-04-13-at-4.06.39-PM.png ads well but wanted to throw this out as test implementation.
@Alan Viars it looks like you called stringify one too many times! Your inflated b64urldecoded payload is a stringified stringified JSON object rather than a stringified JSON object :)
Josh Mandel (Apr 13 2021 at 20:24):
I submitted https://github.com/microsoft/health-cards-validation-SDK/issues/50 to capture this. (FYI @Christian Paquin)
Alan Viars (Apr 13 2021 at 20:51):
Josh Mandel said:
Here is my preview. I'll be working on the BlueButton CARIN activity too. Screen-Shot-2021-04-13-at-4.06.39-PM.png ads well but wanted to throw this out as test implementation.
Alan Viars it looks like you called stringify one too many times! Your inflated b64urldecoded payload is a stringified stringified JSON object rather than a stringified JSON object :)
Oh thanks for spotting that. I hadn't yet fully verified it. Will try to fix today. Trying to rush to get it ready for tomorrow. Actually I was pretty confused at that one point. I din't try to stringify it 2x...
Paul Denning (Apr 13 2021 at 21:02):
Josh Mandel said:
...and I'd certainly suggest that the Patient Empowerment WG might want to register as an interested party.
Alan Viars (Apr 13 2021 at 21:03):
Alan Viars said:
Josh Mandel said:
Here is my preview. I'll be working on the BlueButton CARIN activity too. Screen-Shot-2021-04-13-at-4.06.39-PM.png ads well but wanted to throw this out as test implementation.
Alan Viars it looks like you called stringify one too many times! Your inflated b64urldecoded payload is a stringified stringified JSON object rather than a stringified JSON object :)
Oh thanks for spotting that. I hadn't yet fully verified it. Will try to fix today. Trying to rush to get it ready for tomorrow. Actually I was pretty confused at that one point. I din't try to stringify it 2x...
Does this fix it @Josh Mandel ? shc-1-194642695512372_H0SM4Dg.png
...maybe i can just build a validator directly into my implementation....only so much i can do in 2 days :-)
Abbie Watson (Apr 13 2021 at 21:29):
Well, I seem to have gotten numeric mode working using zlib's deflateRaw
function, and have also gotten some of the correct headers on the jwt.
Screen-Shot-2021-04-13-at-4.23.22-PM.png
But when I try to inflating the payload, I'm running into a invalid block type
error. Can anybody else replicate the invalid block type error?
The documention for 'zlib.deflateRaw()' recommends using result.toString('base64')
in the callback, which could be the source of invalid block types. Is that suppose to be a buffer or a string that's in the payload after deflating?
meh, buffer doesn't seem to do it either. Why would there be a block type error
Matt Printz (Apr 13 2021 at 21:41):
Anyone around able to validate my QR code?
qr.png
Josh Mandel (Apr 13 2021 at 22:03):
Matt Printz said:
Anyone around able to validate my QR code?
@Matt Printz It looks like your numeric encoding (or the JWS you encoded) has some issues:
$ node . -l debug -t qrnumeric -p ./matt
SMART Health Card Validation SDK v0.4sh
QR numeric
|
├─ Debug :
| shc:/... = eyJhbGciOiJFUzIXQ//eyxWCIkRFRiIsImtpZCIP::vQexwY3Mdo<cFCelYSYLVdKdO;ckxBWnQN:vdFdvNEcaeG2vjm9PVDHWXbrjLj9w`;\[ZWH4lwIHpQZbXsUCWPJKcNHT8jGOktJDyYEjHl0evvfPF`EbGt1=AhNHXadr\p1<BJPu<N2ME4AyxeFMCCeXxcO;duuva0=44qbkTZxBmXHRlVfVqmURMQHavm@tiXeZFxHaSYZ43No\WXP~FxoeFzLDf=\M\HFaZ7xKpUOWjBBCelwFyEQuHEajjKzjVKdFoaRdXPEfk2/F|EDnQ2HLJ0Fz0Meb:j1fLXw\ZF3[mRknDjUH[U_MDZCB_FB3~CwEaeDZGE9voY;WOXzfOzkJdz3epubGmsxBAxHOAOaBcad0WAcjDOOfDC8SfUSrbbW1\kYlAa2e;txsrfpyNHerdmAoomwee3PYHYdDkjDYbhTm8FuYUbrMypuwFV3XYQvuV-sXfuWDWl\c[\SgeRgM@JlboGyOWldP/xCf<f[Fj:bf/>ZlZ8pFRjGvpQOPNyDPNEfmFL-eIjacgFjkdODCk7XmOCLJSTmIrNnG<
OQghXSahs0DCMQ.>inIsjuAwYqokAhgm0pY7W0QC\_tWVFDD4D0zKvpLdPl_GBb`>do>n321enx0b`9jOtHp3nM;O[npF6abX3vGj2Qnl3`0fpvbmkgIknDYDDREG>N0t/[xV8Tuwbpps9MDBSQ9dR9sIw
| JWS = eyJhbGciOiJFUzIXQ//eyxWCIkRFRiIsImtpZCIP::vQexwY3Mdo<cFCelYSYLVdKdO;ckxBWnQN:vdFdvNEcaeG2vjm9PVDHWXbrjLj9w`;\[ZWH4lwIHpQZbXsUCWPJKcNHT8jGOktJDyYEjHl0evvfPF`EbGt1=AhNHXadr\p1<BJPu<N2ME4AyxeFMCCeXxcO;duuva0=44qbkTZxBmXHRlVfVqmURMQHavm@tiXeZFxHaSYZ43No\WXP~FxoeFzLDf=\M\HFaZ7xKpUOWjBBCelwFyEQuHEajjKzjVKdFoaRdXPEfk2/F|EDnQ2HLJ0Fz0Meb:j1fLXw\ZF3[mRknDjUH[U_MDZCB_FB3~CwEaeDZGE9voY;WOXzfOzkJdz3epubGmsxBAxHOAOaBcad0WAcjDOOfDC8SfUSrbbW1\kYlAa2e;txsrfpyNHerdmAoomwee3PYHYdDkjDYbhTm8FuYUbrMypuwFV3XYQvuV-sXfuWDWl\c[\SgeRgM@JlboGyOWldP/xCf<f[Fj:bf/>ZlZ8pFRjGvpQOPNyDPNEfmFL-eIjacgFjkdODCk7XmOCLJSTmIrNnG<
OQghXSahs0DCMQ.>inIsjuAwYqokAhgm0pY7W0QC\_tWVFDD4D0zKvpLdPl_GBb`>do>n321enx0b`9jOtHp3nM;O[npF6abX3vGj2Qnl3`0fpvbmkgIknDYDDREG>N0t/[xV8Tuwbpps9MDBSQ9dR9sIw
|
├─ Info :
| shc:/... decoded
|
├─ Warning :
| Numeric QR has leading or trailing spaces
|
JWS-compact
|
└─ Fatal :
Failed to parse JWS-compact data as 'base64url.base64url.base64url' string.
Josh Mandel (Apr 13 2021 at 22:11):
But when I try to inflating the payload, I'm running into a invalid block type error. Can anybody else replicate the invalid block type error?
@Abigail Watson hmm, your result is looking a lot like Matt's (see above). Can you share a raw JWS + numeric encoded value so we can sanity-check that?
Abbie Watson (Apr 13 2021 at 22:21):
Here is the JWT
eyJhbGciOiJFUzI1NiIsInppcCI6IkRFRiJ9.N1pMYmJ0cEFFSVpmeGRyZXRKTFBEZ2x3VlVwUFVSR05Rb3NxVlZHMXJNZXd6UjdjM1RVdWlYajN6dG9RVUM3NkFpMDNhRDMvZlBQUDRaRndhOG1ZYkp5cjdUaEp0cFF4cmlDcXFiVzFOaTYyTzFrN0xhbmpMT2FhaEVTdEtqTE9Mck5oTWNpTFVScVNMU1BqUi9LYWFlWGd0eVBqNzArd3RtM2p0b2kxV1NkNW1nMFRacUFFNVRnVk50bG01QzRrYmxlRHoxaUM0UlduS3dIVEp3M1dPb0tzcE1adGdBcTNpUmsxcFgzUlB5TC8rS3VPUzlrby9vRDJ0ZklGVHhZV3plb25NT2U5Vnh0dTBJSDFtakc1aU5NNFE2ai8rcVpScFFDdk1XQjFZeGg4NlJ5VFErRFlBV0ZhQ0tSNVFraXdnTmxoVzBodWhQaHFCQXF1ejR3a0tZcU9RQSszanJyR2RoaFpDM0RnbTJxcG5XdjNnVzhCWFZVNE13aEpqZmxJOXprbHQ3V2dXSVpNcVNvcGcyQkJCVmZVZHV3S0RDZ1BKemQ5U3BLbEtkbUhoSmY0N1h3cWtaU204OHgwZ3hzMGZiT24vSGZIUUpMbG8yRnhkWGt4OEp5U09oL04wenlMMGlKS0IxM1pYdzNZQStQa2J5THdlTzZEVHlERTdyazVRM0ZvM2dieWg1NXJ3SjhkRHVEWTh1RWlwN3JzUnRYZkdGbE1iaGZSOVBNeXlvT1grSGY5TnNwR3I0S0ROZ3prN1h3U0JyYm05eERVUmp2Z0tneG04NXNRWDJEQmJMSDFMUVNWQVJRWGFTRFpPa25qUXM2Q1VsdS9WcVpMcnRiZER1ME9XNUorenN0dmZhUnZITzNlN2NQbmgzRytaMVQvNk9ZOW1GVlhyVklmZCsvdFFySHB3OHhIU3MwYWVWam0vd1A0MXc1Z3Y4ZGsvUDBC.VoUOOvL_ybatf1fTJsS5v8WL3If7kPfILSNzZxQmvFd0M_zpw3boKc-KC1visi7-vJ--_d2knqI1VayPRRFSwA
And the numeric encoded version:
shc:/567629595326546034602925407728433602870286567675422289286237253760291213346732446429354242525384167645626377645433730392521244557564163417440254138373837536337541634527324329334543559406155613243741554263445261339552612374140326336225673223335937233273456321364023375237655559342644544626785461554052246735425257546463235443256041772534523944763977256933375595364673239525595224413955247168392412325462866394233673925413854413332404216840601231444225634226553564121683377206955637713256735356275414336233424653225375254402525334137654162835326343677376944647563372766755240323406355723865528412467632455355337177542445255265521543933674064707554255877406321839221169386413540776369537612733723293456263264127594541667635358333954084165593553921293823257152625445240833332524552601169544167385425252355624433661254556265873363298412524693725632141345593637138334125695243556539647566417721665542593652272522364325456613765237145544012373638707752652976375376652256723323376553392975542684355613365417759224225454133244652246768456475293443676953273664155543384167335441337354265476366229224164454145274163833723454213056643684062131532471625340596638644433345965442715240537342758744565977322645603957733826452840406732532611763765596242267173424678403126631377456656332322663303832875324674455773345363871365252983927556339774524412763243739557652774137323457332555605642584334329638615962372741244239594374263743362977406537444562552653244605226453834412133564363733945869382644839653374376283383373445667693345964032976446438562437414064675453716556263733433337422329243664293238232532404133423641293742262539372567355258684043325364417055381242544167325465376045243743224124333406669566533355644559406545283977336333533365364587737767152324136733362124534424264163597641626364452270735525257638272163427592840632744442414252643735542033243544456447452670734023212214166403434733150765352715745739297038873114231628571062355728313833774575366473255533250776774653663054030224736070601007329005055562656828441527635373725387420
The validator says Invalid numeric QR header: expected shc:/[0-9]/[0-9]/[0-9]+
But the SMART HealthCard website says a single chunk might produce a QR code like shc:/56762909524320603460292437404460<snipped for brevity>
So, there seems to be a discrepancy between the validator, the documentation, and the compiler. :thinking:
Josh Mandel (Apr 13 2021 at 22:23):
You're using the validator? It should definitely accept single-chunk QRs without a /[0-9]/[0-9]
prefix, because it accepts the examples in the spec (e.g., this one). And if you look at the output I pasted above, it's not saying "Invalid numeric QR header: expected shc:/[0-9]/[0-9]/[0-9]+". Can you share a full invocation example together with inputs?
Abbie Watson (Apr 13 2021 at 22:24):
Well, I only just started using the validator. My usage of the validator maybe hasn't been validated yet...
Josh Mandel (Apr 13 2021 at 22:24):
haha
Josh Mandel (Apr 13 2021 at 22:26):
One quick note, in your JWS example the header lacks a kid
claim.
Abbie Watson (Apr 13 2021 at 22:28):
Mmmm.... cropped the image, and the single-chunk QR is still producing the Invalid numeric QR header
in the validator. Using v0.4.3
Abbie Watson (Apr 13 2021 at 22:29):
Had me eye on the kid
claim next. Putting it in now.
Josh Mandel (Apr 13 2021 at 22:31):
So your numeric encoding should start like (a) but actually starts like (b):
(a) 56762959532654603460292540772804
(b) 56762959532654603460292540772843
Josh Mandel (Apr 13 2021 at 22:32):
It looks like any character that gets encoded as 0\d
(e.g., 04
in this case) is having its 0
stripped before you join it into the numeric encoding.
Josh Mandel (Apr 13 2021 at 22:32):
Each character of the input JWS should produce exactly two characters in the numeric encoding.
Abbie Watson (Apr 13 2021 at 22:33):
Huh.
Abbie Watson (Apr 13 2021 at 22:34):
Well, that's not good.
Abbie Watson (Apr 13 2021 at 22:34):
Lemme see about correcting that....
Josh Mandel (Apr 13 2021 at 22:35):
I captured https://github.com/microsoft/health-cards-validation-SDK/issues/52 as a suggestion for the validator
Josh Mandel (Apr 13 2021 at 22:37):
My slightly code-golf-ey implementation is:
const SMALLEST_B64_CHAR_CODE = 45; / / "-".charCodeAt(0) === 45
jws
.split('')
.map((c) => c.charCodeAt(0) - SMALLEST_B64_CHAR_CODE)
.flatMap((c) => [Math.floor(c / 10), c % 10])
.join(''),
Abbie Watson (Apr 13 2021 at 22:53):
Well, I zero padded each number, and created the following string shc:/
string.
shc:/5676295953265460346029254077280433602870286471674522280928627005412767725626043836033366376163724264126140404128546445413839332853634141372337754003667138635930404028034304406031222909524320603460292437404460573601330467304526280853065526384228734006414045262926383871234124373132413368452725715440634253045566412545333240556344034105396521074264670452255561416011773203412336644542524244763726672252640872402632083343660744066603440525373942377044415404542759263963554139414404364141263864673954414503444029744263556741062004400667695243456752045535452671723643634041413776530658074024413056634168332329645642555953623306542404703322700755653334324341263077553941401270396125295541374144042474426245224241377341036605530424073003043732626760524004264105636344775573410304283204294538772168564041675439553844421277396541374141667642620708322667613838707333652967414145613760127332772542554063295362320355272473406212454526360656627562456412063726673453412140372412255504240554614163416471415343552752772127376345743665330541030440520341685223213754261238364204674023372236434573366537033605336742244126563925374403593453406729542529623641240555407528414145754462450837627121366437644142677454394177552640073664083941613607566225305641597138396277532567345641416553406352394325423763296653233723564320034041407739276769376236034540376954647104386471684505046333381206372474694525370841042541546441635342257455055406542728085425367334252967384271033303453138232073330324074403660540255538422737073927557252400829450359604526756437636765372336694065452955622803382728774024632541276676542763713903373853622473520567073441596556645403382633275661416138624540532629435261457133620432360528775239292233654106542344775327440755395569380571735523327752035976453812303004210537396341310367283424417555247536372432083264290939065808456363253725587638261234422724033665594353653339446425303326672341620408344137075439294254416306422603774440597454035543542467055661457154045805406521285543540833761173416459723977595232413669336441393006453032066276456208755205747353254408444207043342633142636374413959324025207336424507546228063726454556627469310555213536012628441167770377501259312530380377500836402454546268527434250369385733706864500367662925603764650671674404366257440956644275695628565024565953684139727732666056537375076236
Still getting an invalid block type
on inflateRaw() and Invalid numeric QR header
with the validator.
Abbie Watson (Apr 13 2021 at 23:01):
Just tried shc:/1/1/56762959....
with the SDK validator, and no dice...
Abbie Watson (Apr 13 2021 at 23:14):
Well, got a much better error code with the SDK validator by using a .jpg
image instead of .png
. Definitely add a bit of file type detection and error message as a ticket item.
QR image
|
├─ Debug :
| d.jpg = shc:/1/1/5676295953265460346029254077280433602870286471674522280928627005412767725626043836033366376163724264126140404128546445413839332853634141372337754003667138635930404028034304406031222909524320603460292437404460573601330467324464290354242525384167645626377645433730380421273842672441633371542725684041293941401231380575424065375955035876370425363306400644427525443959633264370336033365422645453704552733043363456154063339407336620404392545694062086039044544444141303639257052623304384071715427377442392932330445524104376955652535384340043864632640646308414167524403127756402563372645265240417437034569344041094540030532263273382467693441207437653703532732035277216344395534380541715325553039435921360533745624717156633352390425663862540444645561300463653003453241265961360367673426257638254121410441713963550639414560522321094442717633062172552440735562454438256763564145055577327655264531376567074442043441037538326237354539246955434141374058755343372754242869536433454263247331044524546341733663597140242405552612065540540745267077320554743640456641055567520541413862637738403752424275033843297342233759450425313926706939406723534071245203253655233605336129764241634340427506332467054441412455634406522408053640447455263343424125694506290952054004566333713327293744624133534145224163416544622968364325045440547742633309360555363865676145266370386475754264712656246763332729644127254140633371552563093227674255392128412408285562037338265530386125685406440754620866456071213340674441387076520636064104674053414140542612083177457755054003426328773442676544054509376245083662413455635804450667265304596944656323342358753324247339065909364212634223633841394074326362055443410937436327343812345306337239632135530345603103590534266332380667033424456332612145564075055424122156273307426159655440630732655526414163443739630540393308366229623964540555772125400655335306670339622577532555395627297134243769542736734165255239272970444337405539214332655930300567724027447741042976536254743077552139255907310354054261413831052535452362694024457030036707322544734404587341035876330421673423593038253377324325044160127131065536332325063342550334240869310420743658010739347004240300677175352204236208580469000361771075433326343556446833565277625440363923085812393461717176586936285940525571697259292708671121103261112459527571046754215658
|
├─ Info :
| d.jpg decoded
|
QR numeric
|
├─ Debug :
| shc:/1/1/... = eyJhbGciOiJFUzI1NiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UiLCJ6aXAiOiJERUYifQ.N1pMYmJ0cEFFSVpmeGRyZXRKS1BHSWpEVlNtcHFqUVJTVU9LS2xWUnRhd0gyR1FQN3U3YWxFYThlMmR0Q0NnWGZZR1dHN1NlZjc3NTUvQk11TFZrUk5iT1ZYYVVKQTFsakN1SUttcHRwWTJMN1ZaV1RrdnFPSXU1SmlGUml5VVpaY09zeUFlRGZGaUVwR0ZrOUV6ZU02MGMvSEprOVAwRnR0bHM0azBlYTdOS2VtbFdKTXhBQ2NweEttelNaT1FoSkc1YmdjK1lnK0ZMVGhjQ0ppOGFySFVBV1VtTld3TVZiaDB6YWtyN3BudEUvdkZYSFpleVZ2dzMydGZLRnp4YW1OV0xSMkRPZTErdXVVRUgxbXRHcEIrbmNZWlEvL1ZEclVvQlhtUEE2dG93dUc4ZGszM2cwQUZoV2dpa2VVSklzSURaWWx0SXJvWDRhZ1FLTGsrTUpDbUtEa0FQdDQ2NjJyWVlXUWx3NEp2YVVEdlY3aE52QUYwdGNXWVFrZ3J6a2U1elNtNHJRYkVNbVZCVlVnYkJqQXF1cUczWlN6Q2dQSnpjZGlsSmxxWmtGeEplNHJmVHFVUlNtdFl6MHpWdTBIVE5Idk0vSGdKSjFqc3Y4ck5oZitBNUpYVSsya3Q3V1pUbVVUcG95LzZzd2U0WlIzOWpnY2Z6RkZ5QkVOdlg1Z3pGb1hrYnlDODgxNEEvT3h6QW9lWDlSVTUwMlk2cXV6RXlHOS9Ob3NuTlBPb0ZiL0h2OGlMS3p0OEZlMjBZeUx2cE9BeHN4WjhncUl4MndGVVlYRTl2UTN5QkJkTmc2dzBFU3dNb3p0TkFzbFdTeHJtOERrcHQvVnFaTHJsYXRUdTBXMnhKK2puUHYzV1JybkcwKzdBTFh4L0c2WjVSL2FPZDkrUEZsK0p4MFYvY1gvV0gyN1BpODhKSFNzMXF1Vi9tL3dQNDF3NWd0OE5rL1AwQg.4TOs1E0-ptxPC1Dk5g1r-0jz7xXNGOPeYqNeazkcUQTD5g9TOjttygrQIhUadtruhJH5p8B7Mj8Ehaxt1pcBeg
| JWS = eyJhbGciOiJFUzI1NiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UiLCJ6aXAiOiJERUYifQ.N1pMYmJ0cEFFSVpmeGRyZXRKS1BHSWpEVlNtcHFqUVJTVU9LS2xWUnRhd0gyR1FQN3U3YWxFYThlMmR0Q0NnWGZZR1dHN1NlZjc3NTUvQk11TFZrUk5iT1ZYYVVKQTFsakN1SUttcHRwWTJMN1ZaV1RrdnFPSXU1SmlGUml5VVpaY09zeUFlRGZGaUVwR0ZrOUV6ZU02MGMvSEprOVAwRnR0bHM0azBlYTdOS2VtbFdKTXhBQ2NweEttelNaT1FoSkc1YmdjK1lnK0ZMVGhjQ0ppOGFySFVBV1VtTld3TVZiaDB6YWtyN3BudEUvdkZYSFpleVZ2dzMydGZLRnp4YW1OV0xSMkRPZTErdXVVRUgxbXRHcEIrbmNZWlEvL1ZEclVvQlhtUEE2dG93dUc4ZGszM2cwQUZoV2dpa2VVSklzSURaWWx0SXJvWDRhZ1FLTGsrTUpDbUtEa0FQdDQ2NjJyWVlXUWx3NEp2YVVEdlY3aE52QUYwdGNXWVFrZ3J6a2U1elNtNHJRYkVNbVZCVlVnYkJqQXF1cUczWlN6Q2dQSnpjZGlsSmxxWmtGeEplNHJmVHFVUlNtdFl6MHpWdTBIVE5Idk0vSGdKSjFqc3Y4ck5oZitBNUpYVSsya3Q3V1pUbVVUcG95LzZzd2U0WlIzOWpnY2Z6RkZ5QkVOdlg1Z3pGb1hrYnlDODgxNEEvT3h6QW9lWDlSVTUwMlk2cXV6RXlHOS9Ob3NuTlBPb0ZiL0h2OGlMS3p0OEZlMjBZeUx2cE9BeHN4WjhncUl4MndGVVlYRTl2UTN5QkJkTmc2dzBFU3dNb3p0TkFzbFdTeHJtOERrcHQvVnFaTHJsYXRUdTBXMnhKK2puUHYzV1JybkcwKzdBTFh4L0c2WjVSL2FPZDkrUEZsK0p4MFYvY1gvV0gyN1BpODhKSFNzMXF1Vi9tL3dQNDF3NWd0OE5rL1AwQg.4TOs1E0-ptxPC1Dk5g1r-0jz7xXNGOPeYqNeazkcUQTD5g9TOjttygrQIhUadtruhJH5p8B7Mj8Ehaxt1pcBeg
|
├─ Info :
| shc:/1/1/... decoded
|
├─ Warning :
| Single-chunk numeric QR code should have a header shc:/, not shc:/1/1/
|
JWS-compact
|
├─ Debug :
| JWS.header = {"alg":"ES256","kid":"K6TznxmRCChF9nZocQEHrfUI3HnUUD4qSJ-JXJQB4_U","zip":"DEF"}
| JWS.signature = e133acd44d3ea6dc4f0b50e4e60d6bfb48f3ef15cd18e3de62a35e6b391c5104c3e60f533a3b6dca0ad022151a76daee8491f9a7c07b323f0485ac6dd697017a
|
├─ Error :
| Error inflating JWS payload. Did you use raw DEFLATE compression?
| incorrect header check
|
JWS.payload
|
└─ Fatal :
Failed to parse JWS.payload data as JSON.
Josh Mandel (Apr 13 2021 at 23:33):
That's progress!
Josh Mandel (Apr 13 2021 at 23:35):
Can you share a code example for how you're applying DEFLATE compression?
Josh Mandel (Apr 13 2021 at 23:52):
I've seen similar errors before when (accidentally lossily) converting a JS buffer to a string before encoding.
Abbie Watson (Apr 14 2021 at 00:04):
Yeah, this is where I was having questions about base64
encoding. I've tried utf-8
, binary
, and without any value. None of them seem to work. base64
gives legible tokens, whereas the others blow up the console with buffer noise.
let promiseToCompressRecord = new Promise(function(resolve, reject) {
zlib.deflateRaw(stringifiedRecordToSign, (err, buffer) => {
if (err){
reject(err);
}
if (buffer) {
console.log('Compressed buffer:')
console.log(buffer.toString('base64'));
resolve(buffer.toString('base64'));
}
else {
console.log(err);
}
});
});
Abbie Watson (Apr 14 2021 at 00:32):
If I don't use base64
, the raw buffer gets dumped out like this.
I20210413-19:29:56.709(-5)? signHealthCard.stringifiedRecordToSign {"iss":"https://vaccine-passport.symptomatic.io","nbf":1618360256,"vc":{"@context":["https://www.w3.org/2018/credentials/v1"],"type":["VerifiableCredential","https://smarthealth.cards#health-card","https://smarthealth.cards#immunization"],"credentialSubject":{"fhirVersion":"4.0.1","fhirBundle":{"resourceType":"Bundle","type":"collection","entry":[{"fullUrl":"Immunization/0","resource":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"id":"immunization-mmr","encounter":{"reference":"Encounter/129837645"},"date":"2021-03-05","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose","coding":[{"system":"CVX","code":"208"}]},"resourceType":"Immunization","_id":"nPN82dGSF6gzDdbEu","_document":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"id":"immunization-mmr","encounter":{"reference":"Encounter/129837645"},"date":"2021-03-05","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose","coding":[{"system":"CVX","code":"208"}]},"resourceType":"Immunization","_id":"nPN82dGSF6gzDdbEu"}}}]}}}}
I20210413-19:29:56.710(-5)? signHealthCard.syncedCompressedRecord <Buffer ed 92 db 6e 1a 31 10 86 5f 65 e5 de b4 d2 9e 49 28 e1 aa 94 a4 51 d4 88 a2 d0 a2 4a 55 54 19 7b 00 37 3e 6c 6d ef 12 12 f1 ee 1d ef 42 40 b9 e8 0b b4 ... 491 more bytes>
I20210413-19:29:56.711(-5)? signHealthCard.signedToken eyJhbGciOiJFUzI1NiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UiLCJ6aXAiOiJERUYifQ.77-977-9bhoxEO-_vV9l77-93rTSnkko4aqU77-9UdSI77-90KJKVVQZewA3Pmxt77-9EhLvv73vv70d77-9QkDvv73vv70L77-977-9IO-_ve-_ve-_ve-_vT_vv71nIu-_vSND77-977-977-9cu-_vSxrKGNCQ1JR77-9KmN96raq77-9RlEvWCoM77-977-9XizJsO-_vRfvv71ePy_vv73vv70xaRgZPu-_vQ_vv71oD--_ve-_vQx_77-977-9Nu-_vU3vv73vv73Gru-_vTIvBhnvv73vv71Be0Hvv73Lmu-_ve-_ve-_ve-_vW8rCBlz77-9Yinvv71C77-977-9Re-_ve-_vQ4g77-977-977-9a--_vdKvU0Ytd2_vv71HEh5_77-9Ce-_vWot77-90L7Roe-_ve-_vcKsXu-_vQLvv73vv73vv71aWHTvv73vv71mSO-_ve-_vTwtEBrvv71-77-9Ne-_vRA0Fu-_ve-_vS3vv73vv73vv71j77-9Dxw6IO-_vUjvv73vv71A77-9CRbvv71bbAvJte-_vd-sRO-_vc2J77-9LEfvv70BGO-_ve-_vVNf77-9Fu-_vSoJHkJTG--_vSbvv71f77-9Bu-_ve-_vRJnBjHvv70wH--_vSHvv70LV0nvv71lyJhqThlEMyrvv73vv73vv71lL--_ve-_vQ5wMu-_vVLvv70i77-977-9Libvv73vv73TqSRK77-977-9MzM1bu-_vXbvv70e77-9Du-_ve-_vSgvBu-_ve-_ve-_ve-_ve-_ve-_ve-_vdSHaO-_ve-_vUXvv73vv73vv73vv73vv70t77-977-9Bu-_vWcc77-977-9JB7vv71D77-9Ge-_vdy-NmcpDi3vv71A77-9IHAt77-977-977-9ARxa77-9X--_ve-_ve-_vXZU3Y3vv73vv73vv71u77-977-977-9zJMyeu-_vX8377-9SXHvv70u77-9a--_vUjvv71NRnHvv70q77-9AFFl77-9B--_ve-_ve-_vXYy77-977-9BQ5s77-977-9NxAtLe-_ve-_ve-_vUfvv73vv73vv70877-977-924gbF--_ve-_vQwXeu-_ve-_ve-_vW3vv70lFe-_vTzvv73vv71F77-977-977-977-977-9Ln59GO-_vXtG77-977-9du-_vXo6Ge-_ve-_vXrvv73vv73vv71677-95IurOkTvv71h77-977-9L--_ve-_vQHvv71rB--_ve-_vWEy77-977-9AA.dhgQR7NOEdzODY2OB0ramptc8eSXsAugvmA4eCxJQe_amEqBun1iJq4ngzE-oToGYWZvSuQB-ULg7WcaJd0HkQ
I20210413-19:29:56.712(-5)? verifyHealthCard.quickDecode {
I20210413-19:29:56.712(-5)? header: {
I20210413-19:29:56.713(-5)? alg: 'ES256',
I20210413-19:29:56.713(-5)? kid: 'K6TznxmRCChF9nZocQEHrfUI3HnUUD4qSJ-JXJQB4_U',
I20210413-19:29:56.713(-5)? zip: 'DEF'
I20210413-19:29:56.713(-5)? },
I20210413-19:29:56.713(-5)? payload: '��n\u001a1\u0010�_e�ҞI(᪔�QԈ�ТJUT\u0019{\u00007>lm�\u0012\u0012��\u001d�B@��\u000b�� ����?�g"�#C���r�,k(cBCRQ�*c}궪�FQ/X*\f��^,ɰ�\u0017�^?/��1i\u0018\u0019>�\u000f�h\u000f��\f��6�M��Ʈ�2/\u0006\u0019��A{A�˚����o+\b\u0019s�b)�B��E��\u000e ���k�үSF-wo�G\u0012\u001e�\t�j-�оѡ��¬^�\u0002���ZXt��fH��<-\u0010\u001a�~�5�\u00104\u0016��-���c�\u000f\u001c: �H��@�\t\u0016�[l\u000bɵ�߬D�͉�,G�\u0001\u0018��S_�\u0016�*\t\u001eBS\u001b�&�_�\u0006��\u0012g\u00061�0\u001f�!�\u000bWI�eȘjN\u0019D3*���e/��\u000ep2�R�"��.&��ө$J��335n�v�\u001e�\u000e��(/\u0006�������ԇh��E�����-��\u0006�g\u001c��$\u001e�C�\u0019�ܾ6g)\u000e-�@� p-���\u0001\u001cZ�_���vTݍ���n���̓2z�7�Iq�.�k�H�MFq�*�\u0000Qe�\u0007���v2��\u0005\u000el��7\u0010--���G���<��ۈ\u001b\u0017��\f\u0017z���m�%\u0015�<��E�����.~}\u0018�{F��v�z:\u0019��z���z�䋫:D�a��/��\u0001�k\u0007��a2��\u0000',
I20210413-19:29:56.713(-5)? signature: 'dhgQR7NOEdzODY2OB0ramptc8eSXsAugvmA4eCxJQe_amEqBun1iJq4ngzE-oToGYWZvSuQB-ULg7WcaJd0HkQ'
I20210413-19:29:56.713(-5)? }
I20210413-19:29:56.714(-5)? signHealthCard.verifyCheck ��n1�_e�ҞI(᪔�QԈ�ТJUT{7>lm����B@��
�� ����?�g"�#C���r�,k(cBCRQ�*c}궪�FQ/X*
��^,ɰ��^?/��1i>��h��
��6�M��Ʈ�2/��A{A�˚����os�b)�B��E�� ���k�үSF-wo�G� �j-�оѡ��¬^����ZXt��fH��<-�~�5�4��-���c�: �H��@� �[l
ɵ�߬D�͉�,G���S_��* BS��_���g1�0�!�
WI�eȘjND3*���e/��p2�R�"��.&��ө$J��335n�v����(/�������ԇh��E�����-���g��$�C��ܾ6g)-�@� p-���Z�_���vTݍ���n���̓2z�7�Iq�.�k�H�MFq�*�Qe����v2��l��7--���G���<��ۈ��
���m�%�<��E�����.~}�{F��v�z:��z���z�䋫:D�a��/���k��a2��
I20210413-19:29:56.714(-5)? signHealthCard.verifyCheck typeof string
I20210413-19:29:56.721(-5)? verifyHealthCard.inflatedCheck.error Error: invalid block type
I20210413-19:29:56.721(-5)? at Zlib.zlibOnError [as onerror] (zlib.js:182:17)
I20210413-19:29:56.721(-5)? at processChunkSync (zlib.js:431:12)
I20210413-19:29:56.721(-5)? at zlibBufferSync (zlib.js:168:12)
I20210413-19:29:56.722(-5)? at Object.syncBufferWrapper [as inflateRawSync] (zlib.js:766:14)
I20210413-19:29:56.722(-5)? at /Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40 {
I20210413-19:29:56.722(-5)? errno: -3,
I20210413-19:29:56.722(-5)? code: 'Z_DATA_ERROR'
I20210413-19:29:56.722(-5)? }
I20210413-19:29:56.722(-5)? signedToken eyJhbGciOiJFUzI1NiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UiLCJ6aXAiOiJERUYifQ.77-977-9bhoxEO-_vV9l77-93rTSnkko4aqU77-9UdSI77-90KJKVVQZewA3Pmxt77-9EhLvv73vv70d77-9QkDvv73vv70L77-977-9IO-_ve-_ve-_ve-_vT_vv71nIu-_vSND77-977-977-9cu-_vSxrKGNCQ1JR77-9KmN96raq77-9RlEvWCoM77-977-9XizJsO-_vRfvv71ePy_vv73vv70xaRgZPu-_vQ_vv71oD--_ve-_vQx_77-977-9Nu-_vU3vv73vv73Gru-_vTIvBhnvv73vv71Be0Hvv73Lmu-_ve-_ve-_ve-_vW8rCBlz77-9Yinvv71C77-977-9Re-_ve-_vQ4g77-977-977-9a--_vdKvU0Ytd2_vv71HEh5_77-9Ce-_vWot77-90L7Roe-_ve-_vcKsXu-_vQLvv73vv73vv71aWHTvv73vv71mSO-_ve-_vTwtEBrvv71-77-9Ne-_vRA0Fu-_ve-_vS3vv73vv73vv71j77-9Dxw6IO-_vUjvv73vv71A77-9CRbvv71bbAvJte-_vd-sRO-_vc2J77-9LEfvv70BGO-_ve-_vVNf77-9Fu-_vSoJHkJTG--_vSbvv71f77-9Bu-_ve-_vRJnBjHvv70wH--_vSHvv70LV0nvv71lyJhqThlEMyrvv73vv73vv71lL--_ve-_vQ5wMu-_vVLvv70i77-977-9Libvv73vv73TqSRK77-977-9MzM1bu-_vXbvv70e77-9Du-_ve-_vSgvBu-_ve-_ve-_ve-_ve-_ve-_ve-_vdSHaO-_ve-_vUXvv73vv73vv73vv73vv70t77-977-9Bu-_vWcc77-977-9JB7vv71D77-9Ge-_vdy-NmcpDi3vv71A77-9IHAt77-977-977-9ARxa77-9X--_ve-_ve-_vXZU3Y3vv73vv73vv71u77-977-977-9zJMyeu-_vX8377-9SXHvv70u77-9a--_vUjvv71NRnHvv70q77-9AFFl77-9B--_ve-_ve-_vXYy77-977-9BQ5s77-977-9NxAtLe-_ve-_ve-_vUfvv73vv73vv70877-977-924gbF--_ve-_vQwXeu-_ve-_ve-_vW3vv70lFe-_vTzvv73vv71F77-977-977-977-977-9Ln59GO-_vXtG77-977-9du-_vXo6Ge-_ve-_vXrvv73vv73vv71677-95IurOkTvv71h77-977-9L--_ve-_vQHvv71rB--_ve-_vWEy77-977-9AA.dhgQR7NOEdzODY2OB0ramptc8eSXsAugvmA4eCxJQe_amEqBun1iJq4ngzE-oToGYWZvSuQB-ULg7WcaJd0HkQ
Abbie Watson (Apr 14 2021 at 00:49):
Seems I get the invalid block type error regardless of base64
encoding.
Abbie Watson (Apr 14 2021 at 00:51):
The validator says incorrect header check
, so does this look right?
I20210413-19:29:56.712(-5)? header: {
I20210413-19:29:56.713(-5)? alg: 'ES256',
I20210413-19:29:56.713(-5)? kid: 'K6TznxmRCChF9nZocQEHrfUI3HnUUD4qSJ-JXJQB4_U',
I20210413-19:29:56.713(-5)? zip: 'DEF'
I20210413-19:29:56.713(-5)? },
Abbie Watson (Apr 14 2021 at 01:08):
Per the documentation: "header includes kid equal to the base64url-encoded SHA-256 JWK Thumbprint of the key ."
I just used the keys[0].kid
from the .well-known/jwks.json
file.
Josh Mandel (Apr 14 2021 at 01:25):
There's some confusion in terms here. When you get an "invalid header check" error, this is about the DEFLATE header -- i.e., it's an error telling you that the compressed payload of your JWS hasn't been correctly compressed. (It's not about the JWS "header" that you included in your last post.)
Josh Mandel (Apr 14 2021 at 01:27):
I think you want your promiseToCompressRecord
function to return a Buffer (or, say, a byte array). That way your JWS signing library can:
- Create a signature over that raw data (rather than over a base64 representation of the data)
- Correctly base64url-encoded it to create the sequence of characters between the two
.
s of your JWS
Abbie Watson (Apr 20 2021 at 02:18):
Can anyone confirm this QR code? I think it's fully spec compliant (or close to it). Minimized, iss pointer to valid keys, raw deflated, ES256 signed, with kid field in header, base64url encoded, numeric, and on a QR code. It works with our own scanner, but I'd like to know if anybody else can get the embedded JSON object.
Symptomatic-VaccinePassport-VerrifiedCredential-Coronavirus-Deflated-Signed.png
Matt Printz (Apr 20 2021 at 14:55):
@Abigail Watson This is failing for me using the MS validation tool:
JWS = eyJhbGciOiJFUzI1NiIsInppcCI6IkRFRiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UifQ.ZlpKZmI5TXdGTVcvU25SNUFTbi91NjFwbmlnRm9ZbHBUQ3RVU0dnUHJuUFRtamwyc0oyVU12VzdjNTIwNjdRSCtsSTU5L2gzei9HOVR5Q3NoUksyenJXMlRKS2VjUzRVUmkyenR0WEd4WGJmdEU0M3pBa2VDdzBocUhVTlpYYVZGY1YwVmt6ekVIb081Uk84NTFvNS9PT2cvUGtNMisxMjhXNFNhN05KOGpRckVtNndRdVVFa3picE0zZ0l3ZTFiOURkV2FFUXQyRnJpNGxsRHZVNGcyekRqdHNpazI4YWNtY3ErR1ErUlAveFhKNXFtVStJdjJkZktOenhiV0hiclg4aWQ5MTV2aFNFSDFtdEt1SWpUT0NPby8vcWhVNVZFcnpGb2RXYzRmaHNjdzdGd1NnQmNTMGswVHdpQkdwZzl4U0p5SitWM0kwbHcvY0pJa3BMb0JQUnc2NWpyN0lCcFdva09mYWdkczdmYWZSWTlrcXVhM2d4RGFPayswZjJkU3RoV01tb0RDNllxeGpGWU1pa1Vzd083Um9QS3crRnV2SkprYVFvSDc0M3JqaVpseGxCbjNhZFRJY255V1RHWlhsMWNlbjNGbksvbWFaNUY2U1JLTHdmODd3N3RrWEgyTVplMEpJL0JGNVJ5LzlxRVlmUTRQanZ4Qzg4MTZOZUxncDZpSFRkdm9hdmhTY1pkZ3VYOGZoa3R2cTZpUEhoTGY5Y2ZvMnoyTGpocXc2QzV2NTJIZ1czRkl3YXQwUTZGQ29PYjI3dVFUbWpSOUJTK3g2QTJTT0pKR2pSOGs2VHhwTGtKS20zOStMaXVoTm9NczdKN2l0VDQ5MXo5R0N0amNMTDdjQWhmTDhETGVjS0JGUFQ3Qnc9PQ.DLxOxZ7BG4vcS87qsTpdj_4Am3QXpgtEMewCz83mQkjUJpE_O1s7FhTOtcvHQsxM9281e5DskdwKn_Zv_iaDFQ
JWS-compact
|
├─ Debug :
| JWS.header = {"alg":"ES256","zip":"DEF","kid":"K6TznxmRCChF9nZocQEHrfUI3HnUUD4qSJ-JXJQB4_U"}
| JWS.signature = 0cbc4ec59ec11b8bdc4bceeab13a5d8ffe009b7417a60b4431ec02cfcde64248d426913f3b5b3b1614ceb5cbc742cc4cf76f357b90ec91dc0a9ff66ffe268315
|
├─ Error :
| Error inflating JWS payload. Did you use raw DEFLATE compression?
| incorrect header check
|
JWS.payload
|
└─ Fatal :
Failed to parse JWS.payload data as JSON.
Abbie Watson (Apr 20 2021 at 14:56):
Yeah, I’m thinking the validation tool is in error, and intend to bring it up at the meeting today. The libraries that the SDK tool relies on assume TypeScript and ESM, and is inconsistent on how it handles Buffers across Node/browser environments. But the code seems to work to produce the QR code, and I can inflate and get the embedded data afterwords. It works, but doesn’t validate.
Matt Printz (Apr 20 2021 at 14:59):
Ok. I'll try in my python code.
Matt Printz (Apr 20 2021 at 15:10):
@Abigail Watson Here's the error I got in my python code:
zlib.error: Error -3 while decompressing data: invalid block type
Matt Printz (Apr 20 2021 at 15:14):
@Abigail Watson Looks like you're doing an extra base64 encode on the data:
>>> zlib.decompress(codecs.decode(payload, 'base64'), wbits=-8)
b'{"iss":"https://vaccine-passport.symptomatic.io","nbf":1618879872,"vc":{"@context":["https://www.w3.org/2018/credentials/v1"],"type":["VerifiableCredential","https://smarthealth.cards#health-card","https://smarthealth.cards#immunization"],"credentialSubject":{"fhirVersion":"4.0.1","fhirBundle":{"resourceType":"Bundle","type":"collection","entry":[{"fullUrl":"Immunization/0","resource":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"encounter":{"reference":"Encounter/129837645"},"date":"2021-03-05","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose","coding":[{"system":"CVX","code":"208"}]},"resourceType":"Immunization"}}]}}}}'
Normally, I'd just run:
>>> zlib.decompress(payload, wbits=-8)
Where payload is the decrypted value from JWS.
Abbie Watson (Apr 20 2021 at 15:50):
If I take the base64
encoding out, I get an invalid code lengths set
error.
Abbie Watson (Apr 20 2021 at 15:51):
Here is the encoding without the base64 encoding.
I20210420-10:48:12.489(-5)? ================SIGNING HEALTHCARD=============================
I20210420-10:48:12.490(-5)?
I20210420-10:48:12.490(-5)?
I20210420-10:48:12.490(-5)? ---------------Verified Credential------------------------
I20210420-10:48:12.490(-5)?
I20210420-10:48:12.494(-5)? {
I20210420-10:48:12.494(-5)? iss: 'https://vaccine-passport.symptomatic.io',
I20210420-10:48:12.494(-5)? nbf: 1618933752,
I20210420-10:48:12.494(-5)? vc: {
I20210420-10:48:12.495(-5)? '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
I20210420-10:48:12.495(-5)? type: [
I20210420-10:48:12.495(-5)? 'VerifiableCredential',
I20210420-10:48:12.495(-5)? 'https://smarthealth.cards#health-card',
I20210420-10:48:12.495(-5)? 'https://smarthealth.cards#immunization'
I20210420-10:48:12.495(-5)? ],
I20210420-10:48:12.496(-5)? credentialSubject: { fhirVersion: '4.0.1', fhirBundle: [Object] }
I20210420-10:48:12.496(-5)? }
I20210420-10:48:12.496(-5)? }
I20210420-10:48:12.496(-5)?
I20210420-10:48:12.496(-5)? ---------------FHIR Bundle--------------------------------
I20210420-10:48:12.497(-5)?
I20210420-10:48:12.497(-5)? {
I20210420-10:48:12.497(-5)? resourceType: 'Bundle',
I20210420-10:48:12.497(-5)? type: 'collection',
I20210420-10:48:12.497(-5)? entry: [ { fullUrl: 'Immunization/0', resource: [Object] } ]
I20210420-10:48:12.497(-5)? }
I20210420-10:48:12.498(-5)?
I20210420-10:48:12.498(-5)? ---------------Private Key (PEM)--------------------------
I20210420-10:48:12.498(-5)?
I20210420-10:48:12.502(-5)? -----BEGIN PRIVATE KEY-----
I20210420-10:48:12.503(-5)? MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCBK2WhHJlI1uSa3X6+3
I20210420-10:48:12.503(-5)? fFLXAQTzWm+ICbaJO84ROu+PjQ==
I20210420-10:48:12.503(-5)? -----END PRIVATE KEY-----
I20210420-10:48:12.504(-5)?
I20210420-10:48:12.504(-5)?
I20210420-10:48:12.504(-5)? -----------Public Key (.well-known/jwks.json)-------------
I20210420-10:48:12.504(-5)?
I20210420-10:48:12.504(-5)? {
I20210420-10:48:12.505(-5)? kty: 'EC',
I20210420-10:48:12.505(-5)? use: 'sig',
I20210420-10:48:12.505(-5)? crv: 'P-256',
I20210420-10:48:12.505(-5)? kid: 'K6TznxmRCChF9nZocQEHrfUI3HnUUD4qSJ-JXJQB4_U',
I20210420-10:48:12.505(-5)? x: 'MgI6HswAPwPcrnw7T4erbW1ldlzV5Wh-APaxjW6zEZg',
I20210420-10:48:12.505(-5)? y: 'I7qPPo0Z9-5_9pQ5DWGhYQp-rXDXo4GZywRKNMK4Ndo',
I20210420-10:48:12.505(-5)? alg: 'ES256'
I20210420-10:48:12.505(-5)? }
I20210420-10:48:12.506(-5)?
I20210420-10:48:12.506(-5)?
I20210420-10:48:12.506(-5)? ---------------Stringified Payload------------------------
I20210420-10:48:12.506(-5)?
I20210420-10:48:12.506(-5)? {"iss":"https://vaccine-passport.symptomatic.io","nbf":1618933752,"vc":{"@context":["https://www.w3.org/2018/credentials/v1"],"type":["VerifiableCredential","https://smarthealth.cards#health-card","https://smarthealth.cards#immunization"],"credentialSubject":{"fhirVersion":"4.0.1","fhirBundle":{"resourceType":"Bundle","type":"collection","entry":[{"fullUrl":"Immunization/0","resource":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"encounter":{"reference":"Encounter/129837645"},"date":"2021-03-05","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose","coding":[{"system":"CVX","code":"208"}]},"resourceType":"Immunization"}}]}}}}
I20210420-10:48:12.506(-5)?
I20210420-10:48:12.507(-5)? -------------Raw Deflated Payload (Buffer)----------------
I20210420-10:48:12.507(-5)?
I20210420-10:48:12.507(-5)? <Buffer 7d 92 5f 6f d3 30 14 c5 bf 4a 74 79 01 29 ff b3 6e 6d 9e 28 05 a1 89 69 4c 2b 54 48 68 0f ae 73 d3 9a 39 76 b0 9d 94 32 f5 bb 73 9d b4 eb b4 07 fa 52 ... 446 more bytes>
I20210420-10:48:12.507(-5)?
I20210420-10:48:12.507(-5)? -------------Raw Deflated Payload (Uint8Array)------------
I20210420-10:48:12.508(-5)?
I20210420-10:48:12.508(-5)? ArrayBuffer {
I20210420-10:48:12.508(-5)? [Uint8Contents]: <7d 92 5f 6f d3 30 14 c5 bf 4a 74 79 01 29 ff b3 6e 6d 9e 28 05 a1 89 69 4c 2b 54 48 68 0f ae 73 d3 9a 39 76 b0 9d 94 32 f5 bb 73 9d b4 eb b4 07 fa 52 39 f7 f8 77 cf f1 bd 4f 20 ac 85 12 b6 ce b5 b6 4c 92 9e 71 2e 14 46 2d b3 b6 d5 c6 c5 76 df b4 4e 37 cc 09 1e 0b 0d 21 a8 75 0d 65 76 99 4d 67 45 71 ... 16284 more bytes>,
I20210420-10:48:12.508(-5)? byteLength: 16384
I20210420-10:48:12.508(-5)? }
I20210420-10:48:12.508(-5)?
I20210420-10:48:12.508(-5)? -------------Buffer (Experimental)------------------------
I20210420-10:48:12.508(-5)?
I20210420-10:48:12.509(-5)? <Buffer 7d 92 5f 6f d3 30 14 c5 bf 4a 74 79 01 29 ff b3 6e 6d 9e 28 05 a1 89 69 4c 2b 54 48 68 0f ae 73 d3 9a 39 76 b0 9d 94 32 f5 bb 73 9d b4 eb b4 07 fa 52 ... 446 more bytes>
I20210420-10:48:12.509(-5)?
I20210420-10:48:12.509(-5)? ------------JSON Web Signature (JWS)----------------------
I20210420-10:48:12.509(-5)?
I20210420-10:48:12.509(-5)? eyJhbGciOiJFUzI1NiIsInppcCI6IkRFRiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UifQ.fe-_vV9v77-9MBTFv0p0eQEp77-977-9bm3vv70oBe-_ve-_vWlMK1RIaA_vv71z05o5du-_ve-_ve-_vTLvv73vv71z77-977-977-9B--_vVI577-977-9d--_ve-_vU8g77-977-9Eu-_vc6177-9TO-_ve-_vXEuFEYt77-977-977-977-977-9dt-0Tjfvv70JHgsNIe-_vXUNZXbvv71NZ0VxNe-_vUPvv70577-9T--_vWvvv73vv73vv73vv73vv73vv70zbO-_ve-_vcW7Iu-_vWbvv73vv71pNk3vv73vv70K77-9E0zapO-_ve-_vSEE77-9b--_ve-_vVjvv70R77-9YGvvv73vv71nDe-_vTrvv71sw4zvv70i77-9bhtzZirvv71mPETvv73vv71f77-9aO-_vU7vv73vv71kXyvvv73vv71sYdmtfyF377-9e--_vRXvv70cWO-_vSnvv70iTuOMoO-_ve-_vU5VEu-_vcag1Z3vv73vv71tcAzvv73vv70pAXAtJe-_vTwhBGpg77-9FO-_vcid77-9340k77-977-9CyNJSu-_vRPvv73DrWPvv73vv70D77-9aSU677-977-9du-_ve-_vWrvv71Z77-9SO-_vWp6Mwzvv73vv73vv71E77-9dyphW8moDSzvv73vv70Y77-9YMmkUO-_vQ7vv70aDSoP77-977-977-9Su-_ve-_vSkc77-9N--_vTvvv73vv70ZQ--_vXXvv71O77-9JO-_vWfvv73vv73vv73vv71i77-977-9FXPvv73vv73vv715Fu-_vUXvv71OBu-_ve-_vQ7vv71x77-9Me-_ve-_vSTvv73vv70X77-9cu-_vdqEYe-_vTg-O--_vWvQrxcFPUU7bu-_vUJXw5Pvv73vv70E77-977-977-9Mlp8XUV577-977-977-9P0bvv73vv71dcNSGQXN_Ow8D24pHDFrvv70dChUGN--_vXch77-90KLvv70pfO-_vUFt77-977-9RRo0fO-_ve-_vXHvv73vv70E77-977-9fnxcV0Jt77-9We-_vT1Fau-_vXvvv71-77-977-9MTjvv719OO-_ve-_vRfvv73vv70877-9QArvv73vv70D.OTaX5A-Q82vHG-xELo-_jcU2cpD-vsz_VB3z5twRw3uMkvw-5fagWA_I6U0WBfj0N8KDaiJHCdvpYZHZA4PgEQ
Abbie Watson (Apr 20 2021 at 15:51):
And here is the decoding:
I20210420-10:48:12.510(-5)? ================VERIFY SIGNATURE==========================
I20210420-10:48:12.510(-5)? eyJhbGciOiJFUzI1NiIsInppcCI6IkRFRiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UifQ.fe-_vV9v77-9MBTFv0p0eQEp77-977-9bm3vv70oBe-_ve-_vWlMK1RIaA_vv71z05o5du-_ve-_ve-_vTLvv73vv71z77-977-977-9B--_vVI577-977-9d--_ve-_vU8g77-977-9Eu-_vc6177-9TO-_ve-_vXEuFEYt77-977-977-977-977-9dt-0Tjfvv70JHgsNIe-_vXUNZXbvv71NZ0VxNe-_vUPvv70577-9T--_vWvvv73vv73vv73vv73vv73vv70zbO-_ve-_vcW7Iu-_vWbvv73vv71pNk3vv73vv70K77-9E0zapO-_ve-_vSEE77-9b--_ve-_vVjvv70R77-9YGvvv73vv71nDe-_vTrvv71sw4zvv70i77-9bhtzZirvv71mPETvv73vv71f77-9aO-_vU7vv73vv71kXyvvv73vv71sYdmtfyF377-9e--_vRXvv70cWO-_vSnvv70iTuOMoO-_ve-_vU5VEu-_vcag1Z3vv73vv71tcAzvv73vv70pAXAtJe-_vTwhBGpg77-9FO-_vcid77-9340k77-977-9CyNJSu-_vRPvv73DrWPvv73vv70D77-9aSU677-977-9du-_ve-_vWrvv71Z77-9SO-_vWp6Mwzvv73vv73vv71E77-9dyphW8moDSzvv73vv70Y77-9YMmkUO-_vQ7vv70aDSoP77-977-977-9Su-_ve-_vSkc77-9N--_vTvvv73vv70ZQ--_vXXvv71O77-9JO-_vWfvv73vv73vv73vv71i77-977-9FXPvv73vv73vv715Fu-_vUXvv71OBu-_ve-_vQ7vv71x77-9Me-_ve-_vSTvv73vv70X77-9cu-_vdqEYe-_vTg-O--_vWvQrxcFPUU7bu-_vUJXw5Pvv73vv70E77-977-977-9Mlp8XUV577-977-977-9P0bvv73vv71dcNSGQXN_Ow8D24pHDFrvv70dChUGN--_vXch77-90KLvv70pfO-_vUFt77-977-9RRo0fO-_ve-_vXHvv73vv70E77-977-9fnxcV0Jt77-9We-_vT1Fau-_vXvvv71-77-977-9MTjvv719OO-_ve-_vRfvv73vv70877-9QArvv73vv70D.OTaX5A-Q82vHG-xELo-_jcU2cpD-vsz_VB3z5twRw3uMkvw-5fagWA_I6U0WBfj0N8KDaiJHCdvpYZHZA4PgEQ
I20210420-10:48:12.510(-5)?
I20210420-10:48:12.511(-5)? ------------Decoded Signature-----------------------------
I20210420-10:48:12.511(-5)?
I20210420-10:48:12.511(-5)? {
I20210420-10:48:12.511(-5)? header: {
I20210420-10:48:12.511(-5)? alg: 'ES256',
I20210420-10:48:12.512(-5)? zip: 'DEF',
I20210420-10:48:12.512(-5)? kid: 'K6TznxmRCChF9nZocQEHrfUI3HnUUD4qSJ-JXJQB4_U'
I20210420-10:48:12.512(-5)? },
I20210420-10:48:12.512(-5)? payload: '}�_o�0\u0014ſJty\u0001)��nm�(\u0005��iL+THh\u000f�sӚ9v���2��s���\u0007�R9��w��O ��\u0012�ε�L��q.\u0014F-�����vߴN7�\t\u001e\u000b\r!�u\rev�MgEq5�C�9�O�k������3l��Ż"�f��i6M��\n' +
I20210420-10:48:12.512(-5)? '�\u0013Lڤ��!\u0004�o��X�\u0011�`k��g\r�:�lÌ�"�n\u001bsf*�f<D��_�h�N��d_+��la٭!w�{�\u0015�\u001cX�)�"N㌠��NU\u0012�Ơ՝��mp\f��)\u0001p-%�<!\u0004j`�\u0014�ȝ�ߍ$��\u000b#IJ�\u0013�íc��\u0003�i%:��v��j�Y�H�jz3\f���D�w*a[ɨ\r,��\u0018�`ɤP�\u000e�\u001a\r*\u000f���J��)\u001c�7�;��\u0019C�u�N�$�g����b��\u0015s���y\u0016�E�N\u0006��\u000e�q�1��$��\u0017�r�ڄa�8>;�kЯ\u0017\u0005=E;n�BWÓ��\u0004���2Z|]Ey���?F��]pԆAs;\u000f\u0003ۊG\fZ�\u001d\n' +
I20210420-10:48:12.512(-5)? '\u0015\u00067�w!��)|�Am��E\u001a4|��q��\u0004��~|\\WBm�Y�=Ej�{�~��18�}8��\u0017��<�@\n' +
I20210420-10:48:12.513(-5)? '��\u0003',
I20210420-10:48:12.513(-5)? signature: 'OTaX5A-Q82vHG-xELo-_jcU2cpD-vsz_VB3z5twRw3uMkvw-5fagWA_I6U0WBfj0N8KDaiJHCdvpYZHZA4PgEQ'
I20210420-10:48:12.513(-5)? }
I20210420-10:48:12.513(-5)?
I20210420-10:48:12.513(-5)? -------------Is Verified----------------------------------
I20210420-10:48:12.513(-5)?
I20210420-10:48:12.843(-5)? YES
I20210420-10:48:12.844(-5)?
I20210420-10:48:12.844(-5)? ------------JWS Parts-------------------------------------
I20210420-10:48:12.844(-5)?
I20210420-10:48:12.844(-5)? [
I20210420-10:48:12.844(-5)? 'eyJhbGciOiJFUzI1NiIsInppcCI6IkRFRiIsImtpZCI6Iks2VHpueG1SQ0NoRjluWm9jUUVIcmZVSTNIblVVRDRxU0otSlhKUUI0X1UifQ',
I20210420-10:48:12.844(-5)? 'fe-_vV9v77-9MBTFv0p0eQEp77-977-9bm3vv70oBe-_ve-_vWlMK1RIaA_vv71z05o5du-_ve-_ve-_vTLvv73vv71z77-977-977-9B--_vVI577-977-9d--_ve-_vU8g77-977-9Eu-_vc6177-9TO-_ve-_vXEuFEYt77-977-977-977-977-9dt-0Tjfvv70JHgsNIe-_vXUNZXbvv71NZ0VxNe-_vUPvv70577-9T--_vWvvv73vv73vv73vv73vv73vv70zbO-_ve-_vcW7Iu-_vWbvv73vv71pNk3vv73vv70K77-9E0zapO-_ve-_vSEE77-9b--_ve-_vVjvv70R77-9YGvvv73vv71nDe-_vTrvv71sw4zvv70i77-9bhtzZirvv71mPETvv73vv71f77-9aO-_vU7vv73vv71kXyvvv73vv71sYdmtfyF377-9e--_vRXvv70cWO-_vSnvv70iTuOMoO-_ve-_vU5VEu-_vcag1Z3vv73vv71tcAzvv73vv70pAXAtJe-_vTwhBGpg77-9FO-_vcid77-9340k77-977-9CyNJSu-_vRPvv73DrWPvv73vv70D77-9aSU677-977-9du-_ve-_vWrvv71Z77-9SO-_vWp6Mwzvv73vv73vv71E77-9dyphW8moDSzvv73vv70Y77-9YMmkUO-_vQ7vv70aDSoP77-977-977-9Su-_ve-_vSkc77-9N--_vTvvv73vv70ZQ--_vXXvv71O77-9JO-_vWfvv73vv73vv73vv71i77-977-9FXPvv73vv73vv715Fu-_vUXvv71OBu-_ve-_vQ7vv71x77-9Me-_ve-_vSTvv73vv70X77-9cu-_vdqEYe-_vTg-O--_vWvQrxcFPUU7bu-_vUJXw5Pvv73vv70E77-977-977-9Mlp8XUV577-977-977-9P0bvv73vv71dcNSGQXN_Ow8D24pHDFrvv70dChUGN--_vXch77-90KLvv70pfO-_vUFt77-977-9RRo0fO-_ve-_vXHvv73vv70E77-977-9fnxcV0Jt77-9We-_vT1Fau-_vXvvv71-77-977-9MTjvv719OO-_ve-_vRfvv73vv70877-9QArvv73vv70D',
I20210420-10:48:12.845(-5)? 'OTaX5A-Q82vHG-xELo-_jcU2cpD-vsz_VB3z5twRw3uMkvw-5fagWA_I6U0WBfj0N8KDaiJHCdvpYZHZA4PgEQ'
I20210420-10:48:12.845(-5)? ]
I20210420-10:48:12.845(-5)?
I20210420-10:48:12.845(-5)? ------------JWS Payload-----------------------------------
I20210420-10:48:12.846(-5)?
I20210420-10:48:12.846(-5)? fe-_vV9v77-9MBTFv0p0eQEp77-977-9bm3vv70oBe-_ve-_vWlMK1RIaA_vv71z05o5du-_ve-_ve-_vTLvv73vv71z77-977-977-9B--_vVI577-977-9d--_ve-_vU8g77-977-9Eu-_vc6177-9TO-_ve-_vXEuFEYt77-977-977-977-977-9dt-0Tjfvv70JHgsNIe-_vXUNZXbvv71NZ0VxNe-_vUPvv70577-9T--_vWvvv73vv73vv73vv73vv73vv70zbO-_ve-_vcW7Iu-_vWbvv73vv71pNk3vv73vv70K77-9E0zapO-_ve-_vSEE77-9b--_ve-_vVjvv70R77-9YGvvv73vv71nDe-_vTrvv71sw4zvv70i77-9bhtzZirvv71mPETvv73vv71f77-9aO-_vU7vv73vv71kXyvvv73vv71sYdmtfyF377-9e--_vRXvv70cWO-_vSnvv70iTuOMoO-_ve-_vU5VEu-_vcag1Z3vv73vv71tcAzvv73vv70pAXAtJe-_vTwhBGpg77-9FO-_vcid77-9340k77-977-9CyNJSu-_vRPvv73DrWPvv73vv70D77-9aSU677-977-9du-_ve-_vWrvv71Z77-9SO-_vWp6Mwzvv73vv73vv71E77-9dyphW8moDSzvv73vv70Y77-9YMmkUO-_vQ7vv70aDSoP77-977-977-9Su-_ve-_vSkc77-9N--_vTvvv73vv70ZQ--_vXXvv71O77-9JO-_vWfvv73vv73vv73vv71i77-977-9FXPvv73vv73vv715Fu-_vUXvv71OBu-_ve-_vQ7vv71x77-9Me-_ve-_vSTvv73vv70X77-9cu-_vdqEYe-_vTg-O--_vWvQrxcFPUU7bu-_vUJXw5Pvv73vv70E77-977-977-9Mlp8XUV577-977-977-9P0bvv73vv71dcNSGQXN_Ow8D24pHDFrvv70dChUGN--_vXch77-90KLvv70pfO-_vUFt77-977-9RRo0fO-_ve-_vXHvv73vv70E77-977-9fnxcV0Jt77-9We-_vT1Fau-_vXvvv71-77-977-9MTjvv719OO-_ve-_vRfvv73vv70877-9QArvv73vv70D
I20210420-10:48:12.846(-5)?
I20210420-10:48:12.846(-5)? ------------JWS Payload (atob)----------------------------
I20210420-10:48:12.846(-5)?
I20210420-10:48:12.846(-5)? }�_o�0Å¿Jty)��nm�(��iL+THh�sÓ9v���2��s����R9��w��O ���ε�L��q.F-�����vß´N7�
ev�MgEq5�C�9�O�k������3l��Ż"�f��i6M��
�:�lÃ�"�nf*�f<D��_�h�N��d_+��laÙ!w�{��X�)�"Nã ��NUï¿½Æ Õ��mp
��)p-%�<!j`��È�ß$��
#IJ��Ãc���i%:��v��j�Y�H�jz3
*���J��)�7�;��C�u�N�$�g����b��s���y�E�N���q�1��$���r�Úa�8>;�kЯ=E;n�BWÃ�����2Z|]Ey���?F��]pÔAs;ÛG ���D�w*a[ɨ
Z�
I20210420-10:48:12.847(-5)? 7�w!��)|�Am��E4|��q����~|\WBm�Y�=Ej�{�~��18�}8����<�@
I20210420-10:48:12.847(-5)? ��
I20210420-10:48:12.847(-5)?
I20210420-10:48:12.847(-5)? ------------Payload Buffer (atob)-----------------
I20210420-10:48:12.849(-5)?
I20210420-10:48:12.850(-5)? <Buffer 7d c3 af c2 bf c2 bd 5f 6f c3 af c2 bf c2 bd 30 14 c3 85 c2 bf 4a 74 79 01 29 c3 af c2 bf c2 bd c3 af c2 bf c2 bd 6e 6d c3 af c2 bf c2 bd 28 05 c3 af ... 1423 more bytes>
I20210420-10:48:12.850(-5)?
I20210420-10:48:12.850(-5)? ------------Decompressed Payload--------------------------
I20210420-10:48:12.851(-5)?
I20210420-10:48:12.894(-5)? Exception while invoking method 'signHealthCard' Error: invalid code lengths set
I20210420-10:48:12.895(-5)? at Zlib.zlibOnError [as onerror] (zlib.js:182:17)
I20210420-10:48:12.895(-5)? at processChunkSync (zlib.js:431:12)
I20210420-10:48:12.895(-5)? at zlibBufferSync (zlib.js:168:12)
I20210420-10:48:12.895(-5)? at Object.syncBufferWrapper [as inflateRawSync] (zlib.js:766:14)
I20210420-10:48:12.895(-5)? at packages/symptomatic:vaccine-wallet/server/methods.js:326:35
I20210420-10:48:12.896(-5)? at /Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
I20210420-10:48:12.896(-5)? => awaited here:
I20210420-10:48:12.896(-5)? at Promise.await (/Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:60:12)
I20210420-10:48:12.896(-5)? at Server.apply (packages/ddp-server/livedata_server.js:1638:22)
I20210420-10:48:12.896(-5)? at Server.call (packages/ddp-server/livedata_server.js:1607:17)
I20210420-10:48:12.896(-5)? at packages/symptomatic:vaccine-wallet/server/methods.js:238:16
I20210420-10:48:12.897(-5)? at /Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
Josh Mandel (Apr 20 2021 at 15:55):
@Abigail Watson there are a couple of things to check on here. You want to 1) pass a stringified payload into DEFLATE, 2) pass the binary result of the DEFLATE operation into your JWS signing algorithm as the payload, (e.g., as a Buffer). there's a full example here.
Josh Mandel (Apr 20 2021 at 15:58):
You can get into trouble in (2) if you do either of the following:
- encode the buffer (e.g. base64url encode it) before passing it into your
.sign()
step - stringify the buffer before passing it into your
.sign()
step
Abbie Watson (Apr 20 2021 at 16:02):
Yes, thank you for the example. It relies on ESM and Typescript, with assumptions around Node versions and how Buffers/Uint8Array work. Regardless, here's the code going into deflate and zlib that successfully works:
let vcPayloadString = JSON.stringify(recordToSign);
let vcPayloadString_trimmed = vcPayloadString.trim();
console.log(vcPayloadString_trimmed);
console.log('');
console.log('-------------Raw Deflated Payload (Buffer)----------------')
console.log('');
let deflatedPayload = zlib.deflateRawSync(vcPayloadString_trimmed);
console.log(deflatedPayload);
console.log('')
console.log('-------------Raw Deflated Payload (Uint8Array)------------')
console.log('')
console.log(deflatedPayload.buffer);
console.log('')
console.log('-------------Buffer (Experimental)------------------------')
console.log('')
console.log(Buffer.from(deflatedPayload));
console.log('');
console.log('-------------Payload Base64 String------------------------')
console.log('');
let payload_base64 = deflatedPayload.toString('base64');
console.log(payload_base64);
let json_web_signature = jws.sign({
header: { alg: 'ES256', zip: 'DEF', kid: get(keychain, 'keys[0].kid')},
secret: privatePem,
payload: payload_base64
// encoding: 'base64'
});
Josh Mandel (Apr 20 2021 at 16:02):
For example, these would be mistakes:
> p = require("pako")
> payloadJson = {"a": 1};
> deflated = p.deflateRaw(JSON.stringify(payloadJson))
Uint8Array(10) [
171, 86, 74, 84, 178,
82, 48, 172, 5, 0
]
> payload = Buffer.from(deflated)
<Buffer ab 56 4a 54 b2 52 30 ac 05 00>
> payload_wrong_a = deflated.toString()
'171,86,74,84,178,82,48,172,5,0'
> payload_wrong_b = payload.toString()
'�VJT�R0�\x05\x00'
> payload_wrong_c = payload.toString('base64url')
'q1ZKVLJSMKwFAA'
Josh Mandel (Apr 20 2021 at 16:03):
You can see (a), (b), and (c) are each wrong (for different reasons).
Abbie Watson (Apr 20 2021 at 16:03):
Yes, I've been through many dozens of these alternative errors.
Josh Mandel (Apr 20 2021 at 16:09):
OK -- the 1st (payload
) should work.
Abbie Watson (Apr 20 2021 at 16:10):
As you can see above, I am 1) passing a stringified payload into DEFLATE, and then 2) passing the binary result of the DEFLATE operation into the JWS signing algorithm as the payload, (e.g., as a Buffer).
Abbie Watson (Apr 20 2021 at 16:10):
Also, keep in mind that the pako
library relies on the latest versions of Node and should probably be considered bleeding-edge with breaking changes. Not everybody supports ESM or relies on the Uint8Array
for zlib signing.
Josh Mandel (Apr 20 2021 at 16:44):
As you can see above, I am 1) passing a stringified payload into DEFLATE, and then 2) passing the binary result of the DEFLATE operation into the JWS signing algorithm as the payload, (e.g., as a Buffer).
It looks like your'e passing payload_base64
into the JWS signing algorithm (as a string, with encoding
commented out).
Also, keep in mind that the pako library relies on the latest versions of Node and should probably be considered bleeding-edge
Here's an example using the node zlib
built-in, rather than pako, and runs in versions of node back to v10 (oldest Node.js release currently under maintenance) with only one dependency (node-jose
):
// npm init && npm install node-jose
let zlib = require('zlib');
let jose = require('node-jose');
let signingKey = {
"kty": "EC",
"kid": "3Kfdg-XwP-7gXyywtUfUADwBumDOPKMQx-iELL11W9s",
"use": "sig",
"alg": "ES256",
"crv": "P-256",
"x": "11XvRWy1I2S0EyJlyf_bWfw_TQ5CJJNLw78bHXNxcgw",
"y": "eZXwxvO1hvCY0KucrPfKo7yAyMT6Ajc3N7OkAB6VYy8",
"d": "FvOOk6hMixJ2o9zt4PCfan_UW7i4aOEnzj76ZaCI9Og"
}
jose.JWS
.createSign({ format: 'compact', fields: { zip: 'DEF' }}, signingKey)
.update(zlib.deflateRawSync(JSON.stringify({"iss": "https://example.com", vc: {}})))
.final()
.then(function(r){ console.log(r); })
Josh Mandel (Apr 20 2021 at 16:45):
^ This is working for me (just tweaked the example generator to use zlib instead of pako in this demo). Let me know what happens when you try this @Abigail Watson ?
Abbie Watson (Apr 20 2021 at 17:41):
invalid block type
error.
I20210420-12:41:34.575(-5)? ---------------Verified Credential------------------------
I20210420-12:41:34.575(-5)?
I20210420-12:41:34.579(-5)? {
I20210420-12:41:34.579(-5)? iss: 'http://localhost:3000',
I20210420-12:41:34.579(-5)? nbf: 1618940554,
I20210420-12:41:34.579(-5)? vc: {
I20210420-12:41:34.579(-5)? '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
I20210420-12:41:34.580(-5)? type: [
I20210420-12:41:34.580(-5)? 'VerifiableCredential',
I20210420-12:41:34.580(-5)? 'https://smarthealth.cards#health-card',
I20210420-12:41:34.580(-5)? 'https://smarthealth.cards#immunization'
I20210420-12:41:34.580(-5)? ],
I20210420-12:41:34.580(-5)? credentialSubject: { fhirVersion: '4.0.1', fhirBundle: [Object] }
I20210420-12:41:34.580(-5)? }
I20210420-12:41:34.580(-5)? }
I20210420-12:41:34.581(-5)?
I20210420-12:41:34.581(-5)? ---------------FHIR Bundle--------------------------------
I20210420-12:41:34.581(-5)?
I20210420-12:41:34.581(-5)? {
I20210420-12:41:34.581(-5)? resourceType: 'Bundle',
I20210420-12:41:34.581(-5)? type: 'collection',
I20210420-12:41:34.582(-5)? entry: [ { fullUrl: 'Immunization/0', resource: [Object] } ]
I20210420-12:41:34.582(-5)? }
I20210420-12:41:34.582(-5)?
I20210420-12:41:34.582(-5)? ---------------Signing Key (PEM)--------------------------
I20210420-12:41:34.582(-5)?
I20210420-12:41:34.582(-5)? {
I20210420-12:41:34.582(-5)? kty: 'EC',
I20210420-12:41:34.582(-5)? d: 'hp2_IVKLNhXUZCqz38TBnp4fHqsMq-dVwxDNhnTtRYI',
I20210420-12:41:34.582(-5)? use: 'sig',
I20210420-12:41:34.582(-5)? crv: 'P-256',
I20210420-12:41:34.583(-5)? kid: 'yRwxp3sb7ldGlbGcw42zkcamMCo_9QZqUqKR6ZFQtH8',
I20210420-12:41:34.583(-5)? x: 'ivxR4CWtwm4B0D4Bqbg3YnlQO6SuzF-VFZ66D44IDLA',
I20210420-12:41:34.583(-5)? y: 'T6EdDPqAz9sIBrTXaR0KTFlbQsmdCbV4ZpObVo_80MY',
I20210420-12:41:34.583(-5)? alg: 'ES256'
I20210420-12:41:34.583(-5)? }
I20210420-12:41:34.584(-5)?
I20210420-12:41:34.584(-5)?
I20210420-12:41:34.584(-5)? -----------Public Key (.well-known/jwks.json)-------------
I20210420-12:41:34.584(-5)?
I20210420-12:41:34.584(-5)? {
I20210420-12:41:34.584(-5)? kty: 'EC',
I20210420-12:41:34.584(-5)? use: 'sig',
I20210420-12:41:34.584(-5)? crv: 'P-256',
I20210420-12:41:34.585(-5)? kid: 'yRwxp3sb7ldGlbGcw42zkcamMCo_9QZqUqKR6ZFQtH8',
I20210420-12:41:34.585(-5)? x: 'ivxR4CWtwm4B0D4Bqbg3YnlQO6SuzF-VFZ66D44IDLA',
I20210420-12:41:34.585(-5)? y: 'T6EdDPqAz9sIBrTXaR0KTFlbQsmdCbV4ZpObVo_80MY',
I20210420-12:41:34.585(-5)? alg: 'ES256'
I20210420-12:41:34.585(-5)? }
I20210420-12:41:34.585(-5)?
I20210420-12:41:34.585(-5)?
I20210420-12:41:34.585(-5)? ---------------Stringified Payload------------------------
I20210420-12:41:34.586(-5)?
I20210420-12:41:34.586(-5)? {"iss":"http://localhost:3000","nbf":1618940554,"vc":{"@context":["https://www.w3.org/2018/credentials/v1"],"type":["VerifiableCredential","https://smarthealth.cards#health-card","https://smarthealth.cards#immunization"],"credentialSubject":{"fhirVersion":"4.0.1","fhirBundle":{"resourceType":"Bundle","type":"collection","entry":[{"fullUrl":"Immunization/0","resource":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"encounter":{"reference":"Encounter/129837645"},"date":"2021-03-05","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose","coding":[{"system":"CVX","code":"208"}]},"resourceType":"Immunization"}}]}}}}
I20210420-12:41:34.586(-5)?
I20210420-12:41:34.586(-5)? -------------Raw Deflated Payload (Buffer)----------------
I20210420-12:41:34.587(-5)?
I20210420-12:41:34.587(-5)? <Buffer 7d 92 5f 6f d3 30 14 c5 bf 8a 65 5e 40 ca 1f a7 69 47 9b 27 4a 41 68 62 2a d3 ca 2a 24 b4 07 d7 b9 69 cd 1c 3b d8 4e 4a 99 fa dd b9 4e da 75 da 03 79 ... 439 more bytes>
I20210420-12:41:34.593(-5)?
I20210420-12:41:34.593(-5)? ------------JSON Web Signature (JWS)----------------------
I20210420-12:41:34.593(-5)?
I20210420-12:41:34.593(-5)? eyJ6aXAiOiJERUYiLCJhbGciOiJFUzI1NiIsImtpZCI6InlSd3hwM3NiN2xkR2xiR2N3NDJ6a2NhbU1Db185UVpxVXFLUjZaRlF0SDgifQ.fZJfb9MwFMW_imVeQMofp2lHmydKQWhiKtPKKiS0B9e5ac0cO9hOSpn63blO2nXaA3mJHJ_7u-fcmycqnaMF3XnfFGmqjOBqZ5wvcsYYjajeVLTIrrLpbMwmk3FEO0GLJ_pBGO3hj6fFz77UYe1-v0_2eWLsNh2xbJoKCyVoL7lyaZfRh4j6QwOhYg1WVpJvFCyeNdjrDHI1t34HXPldIrgt3ZvhEIfDf3Wyrlst_3IvjQ4NLxZW7eYXCB-8Vztp0YELmoKOE5ZkCA1fP7a6VBA0FpxprYDvvWN6ujgnoMIohbRAiCg2sAeMheRWqXurUHD9wkga5ngGBrjz3Leux9SNAg8h1J67pfFfZAfoqsKZQUQbrEd6qCmlaxTHNnTBdckFkBVXUnPXsyuwoAOc3g4laYbbOwZvwrS4KTuEuug-ny_SbDSb5u-vxpOgL7kPtyM2ymKWx2zS43-34E6Mi4-58lI8kq-g1OG1CctxOCE78qeBa6ExNgQ9R-u4EFLDwpT9SIZ_ia7md6t48W0dj8hbfF1_irPZO3LSRqS-W84j4hr5CKSxxoPUEblZ3kZ4Age2w_AdkMoCinNGarFNWZLXN6Q0LqxPmFLqbb8rd8BIdZjn-sdwMwRHuw_H6PUP8HKf9IgKfP4B.LD4pYqgHcsphE5h_-erG8ZG00W8c2U0iH_OVrpzolJ_TGkJ8N9FusTOFVY1WXR1qZsEqNVotyGjrajanffC4Fw
I20210420-12:41:34.594(-5)?
I20210420-12:41:34.594(-5)? ================VERIFYING SIGNATURE=======================
I20210420-12:41:34.594(-5)?
I20210420-12:41:34.594(-5)? eyJ6aXAiOiJERUYiLCJhbGciOiJFUzI1NiIsImtpZCI6InlSd3hwM3NiN2xkR2xiR2N3NDJ6a2NhbU1Db185UVpxVXFLUjZaRlF0SDgifQ.fZJfb9MwFMW_imVeQMofp2lHmydKQWhiKtPKKiS0B9e5ac0cO9hOSpn63blO2nXaA3mJHJ_7u-fcmycqnaMF3XnfFGmqjOBqZ5wvcsYYjajeVLTIrrLpbMwmk3FEO0GLJ_pBGO3hj6fFz77UYe1-v0_2eWLsNh2xbJoKCyVoL7lyaZfRh4j6QwOhYg1WVpJvFCyeNdjrDHI1t34HXPldIrgt3ZvhEIfDf3Wyrlst_3IvjQ4NLxZW7eYXCB-8Vztp0YELmoKOE5ZkCA1fP7a6VBA0FpxprYDvvWN6ujgnoMIohbRAiCg2sAeMheRWqXurUHD9wkga5ngGBrjz3Leux9SNAg8h1J67pfFfZAfoqsKZQUQbrEd6qCmlaxTHNnTBdckFkBVXUnPXsyuwoAOc3g4laYbbOwZvwrS4KTuEuug-ny_SbDSb5u-vxpOgL7kPtyM2ymKWx2zS43-34E6Mi4-58lI8kq-g1OG1CctxOCE78qeBa6ExNgQ9R-u4EFLDwpT9SIZ_ia7md6t48W0dj8hbfF1_irPZO3LSRqS-W84j4hr5CKSxxoPUEblZ3kZ4Age2w_AdkMoCinNGarFNWZLXN6Q0LqxPmFLqbb8rd8BIdZjn-sdwMwRHuw_H6PUP8HKf9IgKfP4B.LD4pYqgHcsphE5h_-erG8ZG00W8c2U0iH_OVrpzolJ_TGkJ8N9FusTOFVY1WXR1qZsEqNVotyGjrajanffC4Fw
I20210420-12:41:34.595(-5)?
I20210420-12:41:34.595(-5)? ------------Decoded Signature-----------------------------
I20210420-12:41:34.595(-5)?
I20210420-12:41:34.595(-5)? {
I20210420-12:41:34.595(-5)? header: {
I20210420-12:41:34.595(-5)? zip: 'DEF',
I20210420-12:41:34.595(-5)? alg: 'ES256',
I20210420-12:41:34.596(-5)? kid: 'yRwxp3sb7ldGlbGcw42zkcamMCo_9QZqUqKR6ZFQtH8'
I20210420-12:41:34.596(-5)? },
I20210420-12:41:34.596(-5)? payload: "}�_o�0\u0014ſ�e^@�\u001f�iG�'JAhb*��*$�\u0007i�\u001c;�NJ��ݹN�u�\u0003y�\u001c����ܛ'*��\u0005�y�\u0014i���jg�/r�\u0018���T�Ȯ��l�&�qD;A�'�A\u0018�Ꮷ�Ͼ�a�~�O�yb�6\u001d�l�\n" +
I20210420-12:41:34.596(-5)? `\u000b%h/�ri�ч��C\u0003�b\rVV�o\u0014,�5��\fr5�~\u0007\\�]"�-ݛ�\u0010��u��[-�r/�\u000e\r/\u0016V��\u0017\b\u001f�W;iс\u000b���\u0013�d\b\r_?��T\u00104\u0016�i���cz�8'��(��@�(6�\u0007���V�{�Pp��H\u001a�x\u0006\u0006��ܷ��ԍ\u0002\u000f!Ԟ���_d\u0007�AD\u001b�Gz�)�k\u0014�6t�u�\u0005�\u0015WRs׳+��\u0003��\u000e%i��;\u0006o´�);���>�/�l4���Ɠ�/�\u000f�#6�b��l����N�����R<�����\t�q8!;�k�16\u0004=G�\u0010R��H����w�x�m\u001d��[|]���;r�F��[�#�\u001a�\b��ƃ�\u0011�Y�Fx\u0002\u0007���\u001d��\u0002�sFj�MY��7�4.�O�R�m�+w�Hu����p3\u0004G�\u000f���\u000f�r��\n` +
I20210420-12:41:34.596(-5)? '|�\u0001',
I20210420-12:41:34.596(-5)? signature: 'LD4pYqgHcsphE5h_-erG8ZG00W8c2U0iH_OVrpzolJ_TGkJ8N9FusTOFVY1WXR1qZsEqNVotyGjrajanffC4Fw'
I20210420-12:41:34.596(-5)? }
I20210420-12:41:34.596(-5)? [
I20210420-12:41:34.597(-5)? 'eyJ6aXAiOiJERUYiLCJhbGciOiJFUzI1NiIsImtpZCI6InlSd3hwM3NiN2xkR2xiR2N3NDJ6a2NhbU1Db185UVpxVXFLUjZaRlF0SDgifQ',
I20210420-12:41:34.597(-5)? 'fZJfb9MwFMW_imVeQMofp2lHmydKQWhiKtPKKiS0B9e5ac0cO9hOSpn63blO2nXaA3mJHJ_7u-fcmycqnaMF3XnfFGmqjOBqZ5wvcsYYjajeVLTIrrLpbMwmk3FEO0GLJ_pBGO3hj6fFz77UYe1-v0_2eWLsNh2xbJoKCyVoL7lyaZfRh4j6QwOhYg1WVpJvFCyeNdjrDHI1t34HXPldIrgt3ZvhEIfDf3Wyrlst_3IvjQ4NLxZW7eYXCB-8Vztp0YELmoKOE5ZkCA1fP7a6VBA0FpxprYDvvWN6ujgnoMIohbRAiCg2sAeMheRWqXurUHD9wkga5ngGBrjz3Leux9SNAg8h1J67pfFfZAfoqsKZQUQbrEd6qCmlaxTHNnTBdckFkBVXUnPXsyuwoAOc3g4laYbbOwZvwrS4KTuEuug-ny_SbDSb5u-vxpOgL7kPtyM2ymKWx2zS43-34E6Mi4-58lI8kq-g1OG1CctxOCE78qeBa6ExNgQ9R-u4EFLDwpT9SIZ_ia7md6t48W0dj8hbfF1_irPZO3LSRqS-W84j4hr5CKSxxoPUEblZ3kZ4Age2w_AdkMoCinNGarFNWZLXN6Q0LqxPmFLqbb8rd8BIdZjn-sdwMwRHuw_H6PUP8HKf9IgKfP4B',
I20210420-12:41:34.597(-5)? 'LD4pYqgHcsphE5h_-erG8ZG00W8c2U0iH_OVrpzolJ_TGkJ8N9FusTOFVY1WXR1qZsEqNVotyGjrajanffC4Fw'
I20210420-12:41:34.597(-5)? ]
I20210420-12:41:34.597(-5)?
I20210420-12:41:34.597(-5)? ------------JWS Payload-----------------------------------
I20210420-12:41:34.597(-5)?
I20210420-12:41:34.597(-5)? fZJfb9MwFMW_imVeQMofp2lHmydKQWhiKtPKKiS0B9e5ac0cO9hOSpn63blO2nXaA3mJHJ_7u-fcmycqnaMF3XnfFGmqjOBqZ5wvcsYYjajeVLTIrrLpbMwmk3FEO0GLJ_pBGO3hj6fFz77UYe1-v0_2eWLsNh2xbJoKCyVoL7lyaZfRh4j6QwOhYg1WVpJvFCyeNdjrDHI1t34HXPldIrgt3ZvhEIfDf3Wyrlst_3IvjQ4NLxZW7eYXCB-8Vztp0YELmoKOE5ZkCA1fP7a6VBA0FpxprYDvvWN6ujgnoMIohbRAiCg2sAeMheRWqXurUHD9wkga5ngGBrjz3Leux9SNAg8h1J67pfFfZAfoqsKZQUQbrEd6qCmlaxTHNnTBdckFkBVXUnPXsyuwoAOc3g4laYbbOwZvwrS4KTuEuug-ny_SbDSb5u-vxpOgL7kPtyM2ymKWx2zS43-34E6Mi4-58lI8kq-g1OG1CctxOCE78qeBa6ExNgQ9R-u4EFLDwpT9SIZ_ia7md6t48W0dj8hbfF1_irPZO3LSRqS-W84j4hr5CKSxxoPUEblZ3kZ4Age2w_AdkMoCinNGarFNWZLXN6Q0LqxPmFLqbb8rd8BIdZjn-sdwMwRHuw_H6PUP8HKf9IgKfP4B
I20210420-12:41:34.597(-5)?
I20210420-12:41:34.598(-5)? ------------Payload Buffer -------------------------------
I20210420-12:41:34.598(-5)?
I20210420-12:41:34.598(-5)? <Buffer 66 5a 4a 66 62 39 4d 77 46 4d 57 5f 69 6d 56 65 51 4d 6f 66 70 32 6c 48 6d 79 64 4b 51 57 68 69 4b 74 50 4b 4b 69 53 30 42 39 65 35 61 63 30 63 4f 39 ... 602 more bytes>
I20210420-12:41:34.598(-5)?
I20210420-12:41:34.598(-5)? ------------Decompressed Payload--------------------------
I20210420-12:41:34.598(-5)?
I20210420-12:41:34.653(-5)? Exception while invoking method 'signHealthCard' Error: invalid block type
I20210420-12:41:34.653(-5)? at Zlib.zlibOnError [as onerror] (zlib.js:182:17)
Abbie Watson (Apr 20 2021 at 17:44):
Using the following:
let vcPayloadString = JSON.stringify(recordToSign);
let vcPayloadString_trimmed = vcPayloadString.trim();
console.log(vcPayloadString_trimmed);
let deflatedPayload = zlib.deflateRawSync(vcPayloadString_trimmed);
console.log(deflatedPayload);
let json_web_signature = await jose.JWS
.createSign({ format: 'compact', fields: { zip: 'DEF' }}, signingKey)
.update(deflatedPayload)
.final()
.then(function(result){
return result;
});
Abbie Watson (Apr 20 2021 at 17:46):
And the following on verification and decoding:
const parts = json_web_signature.split('.');
const rawPayload = parts[1].trim();
console.log(rawPayload);
console.log('------------Payload Buffer -------------------------------')
console.log('')
let buffer_from_payload = Buffer.from(rawPayload);
console.log(buffer_from_payload);
console.log('')
console.log('------------Decompressed Payload--------------------------')
console.log('')
const decompressed = zlib.inflateRawSync(buffer_from_payload);
const decompressed_string = decompressed.toString('utf8')
console.log(decompressed_string);
Abbie Watson (Apr 20 2021 at 17:58):
As an aside, I'd like to point out that node-jose
has 200K weekly downloads, whereas the jws
library has 8.8M weekly downloads.
When I say that the 'base64' encoding is used in some places, it's per the document examples and recommendations from the jws
library itself. base64 string encoding is what's recommended and works, not something that I just came up with. People who are recommending 'just put the Buffer directly into the signing algorithm' are assuming and relying on the presence of newer technology and libraries; no different than assuming a particular model of high-end camera.
Why does the spec require the latest Node libraries and TypeScript/ESM and Buffer signatures, but not the stringified base64 signatures that are recommended by a library with 10x more users? Why do we require compression using an 'old-phones' argument, but not base64 encoded strings?
Matt Printz (Apr 20 2021 at 18:33):
Forced base64 encoding would increase the payload size and decrease the amount of data available in the QR code.
Abbie Watson (Apr 20 2021 at 18:38):
Yet it's much more widely supported across environments. Why have we decided that 20% payload size is more important than ubiquity of library use?
Abbie Watson (Apr 20 2021 at 18:57):
For those who are interested, reference implementation in pure ECMAScript available here (not Typescript):
https://github.com/symptomatic/smart-healthcard-payload-utility
Matt Printz (Apr 20 2021 at 19:02):
@Abigail Watson Your JWS value you're creating above: eyJ6aXAiOiJERUYiLCJh...jrajanffC4Fw
decompresses fine for me. (Can't validate since issuer is 'localhost:3000' and I don't have the public key.
Matt Printz (Apr 20 2021 at 19:03):
What happens if you comment out this line: const parts = json_web_signature.split('.');
and just use json_web_signature as the raw payload?
Josh Mandel (Apr 20 2021 at 19:09):
Are you asking why we don't double-base-64-encode the DEFLATE-compressed data, or are you asking why we need compression in the first place?
Abbie Watson (Apr 20 2021 at 19:18):
A little bit of both. I'm suggesting that the compression requirement isn't as seamless and easy to implement in all environments as presented, and has cost/benefits.
Josh Mandel (Apr 20 2021 at 19:34):
Sure -- this comes down to QR scanning on commodity hardware including smartphones -- more data are hard to represent at small physical sizes, and require a steadier hand to scan, etc
Josh Mandel (Apr 20 2021 at 19:38):
BTW with the node-jws
library you mentioned, it's popular but doesn't seem to be actively developed. There's a bugfix that landed in master
3 year ago for this issue (see https://github.com/auth0/node-jws/issues/50 for details). If you really want to use this you can:
// install from GH master branch rather than npm repository
// npm install https://github.com/auth0/node-jws
let zlib = require('zlib');
let jws = require('jws');
const { generateKeyPairSync } = require('crypto');
let keys = generateKeyPairSync("ec",{namedCurve: 'P-256'})
let payload = {iss: "https://example.com", vc: {}}
const signature = jws.sign({
header: { alg: 'ES256', zip: 'DEF', kid: "placeholder for kid"},
payload:zlib.deflateRawSync(JSON.stringify(payload)).toString('base64'),
privateKey: keys.privateKey,
encoding: 'base64'
});
console.log("jws", signature)
Abbie Watson (Apr 20 2021 at 19:41):
:sigh:
Josh Mandel (Apr 20 2021 at 19:43):
(and double-sigh -- I just had to edit the example above to add .toString(base64)
+ encoding: 'base64'
, sorry I missed this initially. the node-jws
API is not the most intuitive!)
Abbie Watson (Apr 20 2021 at 20:41):
Well, I managed to get something working with node-jose
and zlib
. Reference example here:
https://github.com/symptomatic/smart-healthcard-payload-utility/tree/node-jose-and-zlib
1359 character count on the shc:/
Abbie Watson (Apr 20 2021 at 20:49):
Generating the QR code now.
Abbie Watson (Apr 20 2021 at 20:57):
Boom, unsupported algorithm
error again with jose.JWS.createSign()
I20210420-15:56:02.438(-5)? ---------------Verified Credential------------------------
I20210420-15:56:02.438(-5)?
I20210420-15:56:02.444(-5)? {
I20210420-15:56:02.444(-5)? iss: 'https://vaccine-passport.symptomatic.io',
I20210420-15:56:02.444(-5)? nbf: 1618952222,
I20210420-15:56:02.444(-5)? vc: {
I20210420-15:56:02.445(-5)? '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
I20210420-15:56:02.445(-5)? type: [
I20210420-15:56:02.445(-5)? 'VerifiableCredential',
I20210420-15:56:02.445(-5)? 'https://smarthealth.cards#health-card',
I20210420-15:56:02.445(-5)? 'https://smarthealth.cards#immunization'
I20210420-15:56:02.446(-5)? ],
I20210420-15:56:02.446(-5)? credentialSubject: { fhirVersion: '4.0.1', fhirBundle: [Object] }
I20210420-15:56:02.446(-5)? }
I20210420-15:56:02.446(-5)? }
I20210420-15:56:02.446(-5)?
I20210420-15:56:02.447(-5)? ---------------FHIR Bundle--------------------------------
I20210420-15:56:02.447(-5)?
I20210420-15:56:02.447(-5)? {
I20210420-15:56:02.448(-5)? resourceType: 'Bundle',
I20210420-15:56:02.448(-5)? type: 'collection',
I20210420-15:56:02.448(-5)? entry: [ { fullUrl: 'Immunization/0', resource: [Object] } ]
I20210420-15:56:02.448(-5)? }
I20210420-15:56:02.448(-5)?
I20210420-15:56:02.449(-5)? ---------------Signing Key (PEM)--------------------------
I20210420-15:56:02.449(-5)?
I20210420-15:56:02.449(-5)? {
I20210420-15:56:02.449(-5)? kty: 'EC',
I20210420-15:56:02.449(-5)? use: 'sig',
I20210420-15:56:02.449(-5)? crv: 'P-256',
I20210420-15:56:02.450(-5)? kid: 'yRwxp3sb7ldGlbGcw42zkcamMCo_9QZqUqKR6ZFQtH8',
I20210420-15:56:02.450(-5)? x: 'ivxR4CWtwm4B0D4Bqbg3YnlQO6SuzF-VFZ66D44IDLA',
I20210420-15:56:02.450(-5)? y: 'T6EdDPqAz9sIBrTXaR0KTFlbQsmdCbV4ZpObVo_80MY',
I20210420-15:56:02.450(-5)? alg: 'ES256'
I20210420-15:56:02.450(-5)? }
I20210420-15:56:02.451(-5)?
I20210420-15:56:02.451(-5)?
I20210420-15:56:02.451(-5)? -----------Public Key (.well-known/jwks.json)-------------
I20210420-15:56:02.451(-5)?
I20210420-15:56:02.451(-5)? {
I20210420-15:56:02.451(-5)? kty: 'EC',
I20210420-15:56:02.451(-5)? use: 'sig',
I20210420-15:56:02.452(-5)? crv: 'P-256',
I20210420-15:56:02.452(-5)? kid: 'yRwxp3sb7ldGlbGcw42zkcamMCo_9QZqUqKR6ZFQtH8',
I20210420-15:56:02.453(-5)? x: 'ivxR4CWtwm4B0D4Bqbg3YnlQO6SuzF-VFZ66D44IDLA',
I20210420-15:56:02.453(-5)? y: 'T6EdDPqAz9sIBrTXaR0KTFlbQsmdCbV4ZpObVo_80MY',
I20210420-15:56:02.453(-5)? alg: 'ES256'
I20210420-15:56:02.453(-5)? }
I20210420-15:56:02.453(-5)?
I20210420-15:56:02.453(-5)?
I20210420-15:56:02.454(-5)? ---------------Stringified Payload------------------------
I20210420-15:56:02.454(-5)?
I20210420-15:56:02.455(-5)? {"iss":"https://vaccine-passport.symptomatic.io","nbf":1618952222,"vc":{"@context":["https://www.w3.org/2018/credentials/v1"],"type":["VerifiableCredential","https://smarthealth.cards#health-card","https://smarthealth.cards#immunization"],"credentialSubject":{"fhirVersion":"4.0.1","fhirBundle":{"resourceType":"Bundle","type":"collection","entry":[{"fullUrl":"Immunization/0","resource":{"status":"completed","wasNotGiven":false,"patient":{"display":"Candace Salinas","reference":"Patient/100"},"encounter":{"reference":"Encounter/129837645"},"date":"2021-03-05","requester":{"display":"Altick Kelly","reference":"Practitioner/8"},"reported":false,"vaccineCode":{"text":"SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 30 mcg/0.3mL dose","coding":[{"system":"CVX","code":"208"}]},"resourceType":"Immunization"}}]}}}}
I20210420-15:56:02.455(-5)?
I20210420-15:56:02.455(-5)? -------------Raw Deflated Payload (Buffer)----------------
I20210420-15:56:02.455(-5)?
I20210420-15:56:02.456(-5)? <Buffer 7d 92 cd 6e db 30 10 84 5f 45 d8 5e 5a 40 ff 8e 53 5b a7 ba 6e 51 04 0d dc 20 4e 8c 02 45 0e 34 b5 b2 d9 50 a4 4a 52 72 dc c0 ef de a5 64 c7 41 0e e5 ... 445 more bytes>
I20210420-15:56:02.465(-5)? Exception while invoking method 'signHealthCard' Error: unsupported algorithm
I20210420-15:56:02.470(-5)? at JWKBaseKeyObject.value (/Volumes/FhirCode/Code/Covid19/vaccine-passport/packages/vaccine-wallet/.npm/package/node_modules/node-jose/lib/jwk/basekey.js:455:31)
I20210420-15:56:02.470(-5)? at /Volumes/FhirCode/Code/Covid19/vaccine-passport/packages/vaccine-wallet/.npm/package/node_modules/node-jose/lib/jws/sign.js:142:21
I20210420-15:56:02.470(-5)? at Array.map (<anonymous>)
I20210420-15:56:02.470(-5)? at /Volumes/FhirCode/Code/Covid19/vaccine-passport/packages/vaccine-wallet/.npm/package/node_modules/node-jose/lib/jws/sign.js:118:21
I20210420-15:56:02.470(-5)? at /Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
I20210420-15:56:02.471(-5)? => awaited here:
I20210420-15:56:02.471(-5)? at Function.Promise.await (/Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
I20210420-15:56:02.471(-5)? at packages/symptomatic:vaccine-wallet/server/methods.js:197:31
I20210420-15:56:02.471(-5)? at /Users/abigailwatson/.meteor/packages/promise/.0.11.2.8skplc.uqog2++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
Josh Mandel (Apr 20 2021 at 23:56):
I see this is open source? Can you share a minimal reproducible example as a GH link?
Josh Mandel (Apr 20 2021 at 23:57):
e.g. is there a script I can run to get this error? I'm happy to help debug.
Abbie Watson (Apr 21 2021 at 00:01):
Minimal reproduction is at:
https://github.com/symptomatic/smart-healthcard-payload-utility
Josh Mandel (Apr 21 2021 at 00:02):
meteor npm install
Is meteor available through npm, or do I need to install it separately? Do you have a docker environment that works well for this?
Josh Mandel (Apr 21 2021 at 00:03):
Or, would it be possible to add a failing test to https://github.com/symptomatic/smart-healthcard-payload-utility/blob/master/tests/main.js ?
Abbie Watson (Apr 21 2021 at 00:03):
Other way around. Meteor is a build pipeline tool, and orchestrates different versions of npm, bower, cordova, etc. At one point in time it was positioned as .Net for Javascript.
Abbie Watson (Apr 21 2021 at 00:07):
And you know... I'm a bit frustrated right now, but I also need to step back and think whether this functionality goes into a package, or in the main repo. It's occurring to me that the unsupported algorithm
from node-jose
is occurring from within a meteor specific package. Which reiterates my original point, but is also a clue in how to maybe architect around it.
Josh Mandel (Apr 21 2021 at 00:08):
Hmm, I'm not sure I'm following. It'd certainly be nice for the issuance logic (signatures, etc) not to depend on Meteor.
Abbie Watson (Apr 21 2021 at 00:13):
Agreed (although the same could be said of typescript and ESM).
Well, just like you started with first assumptions (i.e. minification, typescript, etc), I've started some first-assumption... hoists into an application environment, supports plugin architecture, etc. I'm trying to parse through those assumptions.
Before you dig into the meteor repo, let me rewind to 15:49 above, and generate the QR code another way. I want to give it a second try, without using the Meteor plugin architecture.
Josh Mandel (Apr 21 2021 at 00:14):
(I shared an example above that uses Node v10 an pure JS.)
Josh Mandel (Apr 21 2021 at 00:14):
Before you dig into the meteor repo, let me rewind
OK. In the background I'm apparently (still!) compiling Mongodb as a prereq for meteor (?) ;-)
Abbie Watson (Apr 21 2021 at 00:15):
Right, but even if it's pure JS, which linker you use can result in different results.
Abbie Watson (Apr 21 2021 at 00:15):
Meteor will provide its own mongo.
Josh Mandel (Apr 21 2021 at 00:15):
What do you mean by "linker" here? (I was just executing my example script with node test.js
)
Abbie Watson (Apr 21 2021 at 00:15):
Meteor is a compiler, like XCode or VisualStudio. It handles all sorts of build pipelines.... babel, webpack, npm, bower, cordova.... and spits out a tech stack and web application framework. Originally developed my MIT alumni.
Josh Mandel (Apr 21 2021 at 00:16):
Apparently the mongo that meteor is providing me (through https://aur.archlinux.org/packages/meteor/) is a the mongo I compile myself :p
Abbie Watson (Apr 21 2021 at 00:17):
https://www.meteor.com/developers/install
Josh Mandel (Apr 21 2021 at 00:17):
I'm not super happy with curl https://install.meteor.com/ | sh
;-)
Josh Mandel (Apr 21 2021 at 00:18):
But we'll see what my package manager gives me when it finishes.
Abbie Watson (Apr 21 2021 at 00:18):
Okay, that got a belly laugh. Yeah, that curl
statement is an MIT affectation. Been there since day 1. I didn't have anything to do with it.
Josh Mandel (Apr 21 2021 at 00:38):
FWIW when I run meteor run
and launch http://localhost:3000 and click the button, I get an alert with a Health Card (no errors).
Josh Mandel (Apr 21 2021 at 00:38):
Oh, maybe I'm on the wrong branch though
Josh Mandel (Apr 21 2021 at 00:38):
(This is master
.)
Abbie Watson (Apr 21 2021 at 00:39):
Yeah, the master branch is using base64 encoding. Er, double encoding, I suppose.
Abbie Watson (Apr 21 2021 at 00:40):
The jws-and-zlib
branch is as close as I got to removing the extra base64
encoding.
Josh Mandel (Apr 21 2021 at 01:01):
https://github.com/jmandel/smart-healthcard-payload-utility/tree/jws-and-zlib builds on that one with the fixes I needed (same technique as my jws + zlib example above). Note that I had to strip out the .vc
claim contents because there were some issues there that I didn't want to debug at this stage.
Abbie Watson (Apr 21 2021 at 01:07):
Right. Whether it's btoa
or .toString("base64")
, it works when it's cast to base64 of some sort. Pretty sure it will break the validator though. This solution is using Buffers, not the more strict Uint8Array approach.
Josh Mandel (Apr 21 2021 at 01:12):
The example in my branch passes the validator (up to the fhirBundle steps).
Abbie Watson (Apr 21 2021 at 01:12):
!
Josh Mandel (Apr 21 2021 at 01:12):
the encoding
has to match the .toString(encoding)
.
Abbie Watson (Apr 21 2021 at 01:13):
Yeah???
Josh Mandel (Apr 21 2021 at 01:13):
And you need node-jws from github, not from npm
Abbie Watson (Apr 21 2021 at 01:13):
Ah, the one with the bugfix?
Josh Mandel (Apr 21 2021 at 01:14):
https://github.com/jmandel/smart-healthcard-payload-utility/commit/c89a634e7dc9caa5e494d071261d64eb5fca9f2a has the full set of changes (but there's a bit of line noise in package.json; the only important change was from "jws": "4.0.0"
to "jws": "github:auth0/node-jws",
Abbie Watson (Apr 21 2021 at 01:15):
Jinkies! What's that syntax???? Is that GitHub packages?
Josh Mandel (Apr 21 2021 at 01:15):
Yeah, that means "Get the current default branch from GH"
Abbie Watson (Apr 21 2021 at 01:16):
And that has the bugfix merged in? That hasn't been published to NPM?
Josh Mandel (Apr 21 2021 at 01:17):
Correct -- see my links above!
Abbie Watson (Apr 21 2021 at 01:17):
Got lucky, then; because that branch might not have been merged in. Super cool, nonetheless. Also, that qualifies as some serious git-fu.
Abbie Watson (Apr 21 2021 at 01:18):
(Learning something new today)
Abbie Watson (Apr 21 2021 at 01:35):
Haven't been able to replicate the successful validation test yet (see issue #67), but generated the following QR code using the updated library:
Screen-Shot-2021-04-20-at-8.33.34-PM.png
Josh Mandel (Apr 21 2021 at 01:37):
That QR looks like >V22, BTW. (The validator should pick that up when you get to it.)
Abbie Watson (Apr 21 2021 at 01:41):
1717 characters. But I can trim some of the JSON content down.
Josh Mandel (Apr 21 2021 at 01:44):
1711 JWS chars, or numeric encoding chars?
Abbie Watson (Apr 21 2021 at 01:47):
Numeric encoding chars.
Abbie Watson (Apr 21 2021 at 01:47):
It's suppose to be <1200 numeric, right?
Josh Mandel (Apr 21 2021 at 01:48):
<~1200 JWS characters, which is <~2400 digits
Josh Mandel (Apr 21 2021 at 01:49):
(but this only works if you use numeric encoding for this segment of your QR -- if you use binary encoding for the digits, you spend 8 bits on each.)
Abbie Watson (Apr 21 2021 at 03:30):
Okay, another QR code:
Screen-Shot-2021-04-20-at-10.10.41-PM.png
And the JWS for it:
eyJhbGciOiJFUzI1NiIsInppcCI6IkRFRiIsImtpZCI6InlSd3hwM3NiN2xkR2xiR2N3NDJ6a2NhbU1Db185UVpxVXFLUjZaRlF0SDgifQ.dZFPbxMxEMW_ijVcQPJudpNS2r2VcKlUFdRAhIR6cO1JY_A_ebwRocp373iTogoJX1az8-Y3z89PYIlggG0piYbZbKe0tgGbpIhSzKWlvU8lelWsbm0ECeFhA0N_3l9cfjg77xYSdhqGJyj7hDD8-Asir3LZonJl22qVDb05Fk0tGPN_nfV-DPYPb4wB7iXojAZDscqtxoefqEtdt9navMZMVTPAWdu1PUPr349jMA6rJiPFMWv8OlmDU0OerIKOzjGtEiTwgrxn_0wenfuWHQte5oeOBS9FBVNRZaQJ4ZPDgvVCiQ0zpfaNpeQU42CpglEaxUo5GxRNnA1mDBX0esGBczxGv4xmWlLwN8NgdXW3apaf181cvOXP9aemv3wnTlop_N3tlRSU7C8UKceCNkhxc_tFcoWEeceudig2GVm86ITXj7OuXfgbYSLVMHQ0NjxON6c9FfTV9fr7sVNNzrsLONwf5L9xXr9-ptrVMRtOok7M-6ZbNN17OPAkn2c.xyphEsnZ2axUwFTd_72gdIOY8JecQdSvhM9zcs4o--HW_wC4Wns_4sIeffXcD0C-0ZDfXqaf4iP02fFbiT1L2A
Validator is complaining about wrong number of QR segments and the kid
in the thumbprint. But the entire notion of multi-segment QR codes is a bit questionable to begin with, and visual inspection of the kid from the jws.decode()
show that it's correct. So, I'm calling it done for the time being. I've spent a week longer than anticipated dealing with this validator's questionable assumptions and demands, and am frustrated with it and this IG and its premises. I've got to put this down and concentrate on other things for a bit.
Screen-Shot-2021-04-20-at-10.12.53-PM.png
Thank you for the help and patience, Josh. I appreciate the help more than I'm probably able to articulate right now.
Josh Mandel (Apr 21 2021 at 03:56):
Thsnks Abigail for the feedback and pushing through this! Keep in mind that the validator is a work in progress, and the team has built several enhancement based on connectathon feedback. It's always helpful to separate out issues of 1) "I think the validator is giving me an incorrect result" from 2) "I disagree with a design decision in the spec that the validator is enforcing".
For the errors above, it looks like your kid
may not be a properly computed JWK thumbprint, and like your QR uses one segment instead of two segments. So I think the validator is doing its job, and your comments are in category (2) -- but please correct me if that's wrong (I'm looking through these by eye only).
Christian Paquin (Apr 21 2021 at 13:15):
Josh Mandel said:
That QR looks like >V22, BTW. (The validator should pick that up when you get to it.)
The validator doesn't yet check for >V22; working on it...
Abbie Watson (Apr 21 2021 at 13:47):
Oh gads, please make it a warning, not an error.
Christian Paquin (Apr 21 2021 at 14:59):
Abbie Watson said:
Oh gads, please make it a warning, not an error.
The feature hasn't been implemented yet, only the unit test; it indeed expects a warning in this case (since the spec doesn't use a SHALL for the QR version)
Last updated: Apr 12 2022 at 19:14 UTC