Search code examples
node.jsjwthl7-fhirjwksmart-on-fhir

Validation of Smart Health Card token fails


I am writing below code to get jwt token, which I want to validate with the SMART Health Cards Validation SDK

var jose = require("node-jose");
const {JWS} = require("node-jose");


async function a1(){
    try {
    
const keystore={
    keys: [
        {
            kty: 'EC',
            d: 'gLkNmSBFWR67hEu62eVfVWhFbLGl309jOszsocqbexE',
            use: 'sig',
            crv: 'P-256',
            kid: '6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1',
            x: '5R2yrryD1ztBYnyKyQF5r5kzPUjnVnmR5pMe7H9ykNU',
            y: 'VaqqjG0N2rSuijP9P9QiOjX4XEhIl8k8fzA6FZTSMhY',
            alg: 'ES256'
        }
    ]
}

const ks = await jose.JWK.asKeyStore(keystore);
const rawKey = ks.get(keystore.keys[0].kid)
const key =  await jose.JWK.asKey(rawKey);

const payload =JSON.stringify({ "iss" : "https://spec.smarthealth.cards/examples/issuer", "sub": "1234567890", "name": "John Doe", "iat": 1516239022});

    const token =await jose.JWS.createSign( {format: 'compact'},key).update(payload, "utf8").final();

    console.log(token);
    }catch (err) {
    console.log(err);
  }
    
}
a1();

I am getting token:

eyJhbGciOiJFUzI1NiIsImtpZCI6IjZkODU4MTAyNDAyZGJiZWIwZjliYjcxMWUzZDEzYTEyMjk2ODQ3OTJkYjQ5NDBkYjBkMGU3MWMwOGNhNjAyZTEifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkVyaWMgRC4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE1MTYyMzkwMjJ9.dbjb9YHFCWaFYGQYyGDDL1iFvqk1Ed9k3-PAhVx4NtvFml1q0VcpW854IXW_J47f6Vf1otm2WeftVQHjY3K4vg

When I use this command:

node . --path C:\Users\User\Documents\Saguaro\health-cards-validation-SDK-main\jws.text --type jws

in sdk file, I get below errors:

Error
│ · JWS header missing 'zip' property.
│ · Error inflating JWS payload. Did you use raw DEFLATE compression?
│ incorrect header check
│ · JWS verification failed: can't find key with 'kid' = 6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1 in issuer set

Please let me know what issuer set and how to put kid value in issuer set. Also, what is meant by deflate compression and how to remove this problem. Please note: Above is pseudo code.


Solution

  • You got basically two main errors:

    The first one (I count these two messages as part of one error)

    · JWS header missing 'zip' property.
    · Error inflating JWS payload. Did you use raw DEFLATE compression?

    means, that your token is not in the correct format.

    Smart Health Cards require a compressed payload, using the DEFLATE (see RFC1951) algorithm, and a "zip" header with the value "DEF" to show that the payload is compressed, something I have only seen defined in the JWE RFC, but not for JWS. Most JWT libraries probably don't offer deflating payload for signed tokens, and node-jose also only supports this for JWE, therefore it has to be done manually.

    To achieve that, you can use zlib to compress the payload and manually add a "zip":"DEF" to the header:

    const zlib = require("zlib");
    ...
    const payload = "{...}"  // very long payload, see example in https://mikkel.ca/blog/digging-into-quebecs-proof-of-vaccination/
    const payloadBuf = zlib.deflateRawSync(payload)
    const token =await jose.JWS.createSign({alg: 'ES256', fields: { zip: 'DEF' }, format: 'compact'}, key).update(payloadBuf, "utf8").final();
    

    The second error is:

    JWS verification failed: can't find key with 'kid' = 6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1 in issuer set

    That means that for verification of your token the public key, identified by the given keyId (kid) is needed and that key should be provided in your "issuer set".

    According to the linked documentation the validator tool has a parameter

     -k, --jwkset <key>         path to trusted issuer key set
    

    further down described as: a JSON Web Key (JWK) Set, encoding the issuer public signing key

    So basically you have to store your keystore in a file (e.g. issuerPublicKeys.json) as a JWKS (JSON Web Key Set) like shown below and provide the path to that file in a parameter. During validation of your token, the validator will read the kid from the token header and try to find the corresponding key in the provided key set.

    {
      "keys":
        [
          {
            "kty": "EC",
            "kid": "6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1",
            "use": "sig",
            "alg": "ES256",
            "crv": "P-256",
            "x"  : "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74",
            "y"  : "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI"
          }
        ]
    }
    

    Note: the d value (private key) is not included, you're only supposed to show the public key.

    I downloaded the package, added some more required data to the payload (example found here), tested it and first got the above mentioned error, which disappeared after providing the parameter --jwkset issuerPublicKeys.json:

    node . --path jws.txt --type jws --jwkset issuerPublicKeys.json
    

    That means, the jwt can be validated with the public key!

    The Keyset itself can also be validated:

    node . --type jwkset --path issuerPublicKeys.json
    

    With the current values the result will be:

    Validate Key-Set

    └─ Error
    ·
    key[6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1]: >'kid' does not match thumbprint in issuer key. expected: a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg, actual:
    6d858102402dbbeb0f9bb711e3d13a1229684792db4940db0d0e71c08ca602e1
    Validation completed

    The mentioned thumbprint is identical with the kid. So when you replace the kid with the value "a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg", the error disappears:

    Validate Key-Set
    Validation completed