certificate-fhir-convertor
Version:
Vaccination context for verifiable certificate
188 lines (159 loc) • 10.2 kB
JavaScript
const {certificateToFhirJson, certificateToSmartHealthJson, validateSignedFhirJson, validateQRContent} = require("../fhir-convertor");
const rs = require("jsrsasign");
const config = require('../configs/config');
// const privateKeyPem = '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAnXQalrgztecTpc+INjRQ8s73FSE1kU5QSlwBdICCVJBUKiuQUt7s+Z5epgCvLVAOCbP1mm5lV7bfgV/iYWDio7lzX4MlJwDedWLiufr3Ajq+79CQiqPaIbZTo0i13zijKtX7wgxQ78wT/HkJRLkFpmGeK3za21tEfttytkhmJYlwaDTEc+Kx3RJqVhVh/dfwJGeuV4Xc/e2NH++ht0ENGuTk44KpQ+pwQVqtW7lmbDZQJoOJ7HYmmoKGJ0qt2hrj15uwcD1WEYfY5N7N0ArTzPgctExtZFDmituLGzuAZfv2AZZ9/7Y+igshzfB0reIFdUKw3cdVTzfv5FNrIqN5pwIDAQABAoIBAHPILMUoLt5UTd5f/YnebqgeCRNAmGOBcwk7HtbMqQoGF93qqvZFd30XOAJZ/ncTpz77Vl95ToxxrWk1WQLCe+ZpOK3Dgk5sFSm8zXx1T64UBNPUSnWoh37C1D39+b9rppCZScgnxlyPdSLy3h3q8Hyoy+auqUEkm/ms5W2lT3fJscyN1IAyHrhsOBWjl3Ilq5GxBo5tbYv/Fb1pQiP/p2SIHA1+2ASXNYQP100F5Vn0V6SFtBXTCQnwcvbP083NvlGxs9+xRs3MCUcxCkKepWuzYwOZDmu/2yCz1/EsP6wlsYEHmCZLdIb0tQt0caqzB/RoxfBpNRIlhOtqHvBzUgECgYEAzIRn5Y7lqO3N+V29wXXtVZjYWvBh7xUfOxAwVYv0rKI0y9kHJHhIrU+wOVOKGISxBKmzqBQRPvXtXW8E0/14Zz82g60rRwtNjvW0UoZAY3KPouwruUIjAe2UnKZcQ//MBTrvds8QGpL6nxvPsBqU0y2K+ySAOxBtNtGEjzv8nxUCgYEAxRbMWukIbgVOuQjangkfJEfA1UaRFQqQ8jUmT9aiq2nREnd4mYP8kNKzJa9L7zj6Un6yLH5DbGspZ2gGODeRw3uVFN8XSzRdLvllNEyiG/waiysUtXfG2DPOR6xD8tXXDMm/tl9gTa8cbkvqYy10XT9MpfOAsusEZVmc0/DBBMsCgYAYdAxoKjnThPuHwWma5BrIjUnxNaTADWp6iWj+EYnjylE9vmlYNvmZn1mWwSJV5Ce2QwQ0KJIXURhcf5W4MypeTfSase3mxLc1TLOO2naAbYY3GL3xnLLK3DlUsZ9+kes3BOD097UZOFG3DIA8sjDxPxTLCoY6ibBFSa/r4GRIMQKBgQCranDCgPu79RHLDVBXM0fKnj2xQXbd/hqjDmcL+Xnx7E7S6OYTXyBENX1qwVQh9ESDi34cBJVPrsSME4WVT3+PreS0CnSQDDMfr/m9ywkTnejYMdgJHOvtDuHSpJlUk3g+vxnm3H0+E5d+trhdGiOjFnLrwyWkd5OTMqWcEEFQkQKBgFfXObDz/7KqeSaAxI8RzXWbI3Fa492b4qQUhbKYVpGn98CCVEFJr11vuB/8AXYCa92OtbwgMw6Ah5JOGzRScJKdipoxo7oc2LJ9sSjjw3RB/aWl35ChvnCJhmfSL8Usbj0nWVTrPwRLjMC2bIxkLtnm9qYXPumW1EjEbusjVMpN\n-----END RSA PRIVATE KEY-----\n';
// const publicKeyPem = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnXQalrgztecTpc+INjRQ8s73FSE1kU5QSlwBdICCVJBUKiuQUt7s+Z5epgCvLVAOCbP1mm5lV7bfgV/iYWDio7lzX4MlJwDedWLiufr3Ajq+79CQiqPaIbZTo0i13zijKtX7wgxQ78wT/HkJRLkFpmGeK3za21tEfttytkhmJYlwaDTEc+Kx3RJqVhVh/dfwJGeuV4Xc/e2NH++ht0ENGuTk44KpQ+pwQVqtW7lmbDZQJoOJ7HYmmoKGJ0qt2hrj15uwcD1WEYfY5N7N0ArTzPgctExtZFDmituLGzuAZfv2AZZ9/7Y+igshzfB0reIFdUKw3cdVTzfv5FNrIqN5pwIDAQAB\n-----END PUBLIC KEY-----\n';
// ECDSA key
//
// generate key
// ssh-keygen -t ecdsa -m pem
//
// get public key in pem format
// openssl ec -in /Users/USER_NAME/.ssh/id_ecdsa -pubout
var k1PubP8PEM = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEftkMDoidplcHjVwxbggb8/xdXJQX2S1hib34pFzu1EyifUNAAhcQCQOEkbjaiLSps4GGF+pH4t1GD3rAf3F67g==" +
"-----END PUBLIC KEY-----";
var k1PrvP8PPEM = "-----BEGIN EC PRIVATE KEY-----\n" +
"MHcCAQEEIDMzz0wbs+8IB3EC7ymiwevUyeA0NQyWCaX2A6k3s/y5oAoGCCqGSM49AwEHoUQDQgAEftkMDoidplcHjVwxbggb8/xdXJQX2S1hib34pFzu1EyifUNAAhcQCQOEkbjaiLSps4GGF+pH4t1GD3rAf3F67g==\n" +
"-----END EC PRIVATE KEY-----";
const cert2 = {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://cowin.gov.in/credentials/vaccination/v1"
],
"type": [
"VerifiableCredential",
"ProofOfVaccinationCredential"
],
"credentialSubject": {
"type": "Person",
"id": "did:in.gov.uidai.aadhaar:123456",
"refId": "12346",
"name": "ABC Name",
"gender": "Male",
"age": "34",
"nationality": "Indian",
"address": {
"streetAddress": "",
"streetAddress2": "",
"district": "",
"city": "",
"addressRegion": "",
"addressCountry": "IN",
"postalCode": ""
}
},
"issuer": "https://cowin.gov.in/",
"issuanceDate": "2021-01-15T17:21:13.117Z",
"evidence": [
{
"id": "https://cowin.gov.in/vaccine/undefined",
"feedbackUrl": "https://cowin.gov.in/?undefined",
"infoUrl": "https://cowin.gov.in/?undefined",
"type": [
"Vaccination"
],
"batch": "MB3428BX",
"vaccine": "Covaxin",
"manufacturer": "COVPharma",
"date": "2020-12-02T19:21:18.646Z",
"effectiveStart": "2020-12-02",
"effectiveUntil": "2025-12-02",
"certificateId": "1234796",
"dose": 1,
"totalDoses": 2,
"verifier": {
"name": "Sooraj Singh"
},
"facility": {
"name": "ABC Medical Center",
"address": {
"streetAddress": "ABC",
"streetAddress2": "",
"district": "XYZ",
"city": "PQR",
"addressRegion": "DEF",
"addressCountry": "IN",
"postalCode": ""
}
}
}
],
"nonTransferable": "true",
"proof": {
"type": "RsaSignature2018",
"created": "2021-01-15T17:21:13Z",
"verificationMethod": "did:india",
"proofPurpose": "assertionMethod",
"jws": "eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..mJlHZZRD7VQwVJchfI21ZavjxNKglbf3LSaF1SAjELOWn9MARALkugsmOzG0mBon9R7zXSVPkPM8EDbUZxR4FsRlAFFszFv-0BjyAeIqRv-9MRnlm4cScQi8aCBgBnvsWfNIE175cGNbPUluVv5n6G66tVinioL5IL6uCZNQnSGp4jJrEAZa0t5s3jXfq7soHz1LTfQbLs7cH5-fDi3JW1-WeF4_ELy_9l_OxAc2CoACqYLOLJB-NnPsnz2bwAvH8yXHsjZJphzaBNqpn8DmJvcRHzhz7OjpGfhyouiOyGo_XncadFmftqwfilJkC1EISkSb6QVsyhHLOudY4PTTaA"
}
};
const meta = {
"diseaseCode": "COVID-19",
"publicHealthAuthority": "Govt Of India"
}
test('should convert W3C certificate json to fhir json', async () => {
let fhirCert = await certificateToFhirJson(cert2, k1PrvP8PPEM, meta);
console.log(JSON.stringify(fhirCert));
expect(fhirCert.entry[0].fullUrl).toContain('urn:uuid:');
expect(fhirCert.entry[0].resourceType).toBe('Composition');
expect(fhirCert.entry[0].subject.reference).toBe(fhirCert.entry[2].fullUrl);
expect(fhirCert.entry[0].attester[0].party.reference).toBe(fhirCert.entry[1].fullUrl);
expect(fhirCert.entry[0].section[0].entry[0].reference).toBe(fhirCert.entry[3].fullUrl);
expect(fhirCert.entry[1].fullUrl).toContain('urn:uuid:');
expect(fhirCert.entry[1].resourceType).toBe('Organization');
expect(fhirCert.entry[1].name).toBe(cert2.evidence[0].facility.name);
expect(fhirCert.entry[2].fullUrl).toContain('urn:uuid:');
expect(fhirCert.entry[2].resourceType).toBe('Patient');
expect(fhirCert.entry[2].name[0].text).toBe(cert2.credentialSubject.name);
expect(fhirCert.entry[2].gender).toBe(cert2.credentialSubject.gender.toLowerCase());
expect(fhirCert.entry[3].fullUrl).toContain('urn:uuid:');
expect(fhirCert.entry[3].resourceType).toBe('Immunization');
expect(fhirCert.entry[3].occurrenceDateTime).toBe(cert2.evidence[0].date);
expect(fhirCert.entry[3].lotNumber).toBe(cert2.evidence[0].batch);
expect(fhirCert.entry[3].expirationDate).toBe(cert2.evidence[0].effectiveUntil);
expect(fhirCert.entry[3].protocolApplied[0].doseNumberPositiveInt).toBe(parseInt(cert2.evidence[0].dose));
expect(fhirCert.entry[3].protocolApplied[0].seriesDosesPositiveInt).toBe(parseInt(cert2.evidence[0].totalDoses));
expect(fhirCert.entry[4].fullUrl).toContain('urn:uuid:');
expect(fhirCert.entry[4].resource.resourceType).toBe('DocumentReference');
expect(fhirCert.signature.sigFormat).toBe("application/jose");
expect(fhirCert.signature.data.split(".").length).toBe(3);
});
test('should convert W3C certificate json to Smart Health Card json', async () => {
let shcCert = await certificateToSmartHealthJson(cert2, meta);
console.log(JSON.stringify(shcCert));
expect(shcCert.credentialSubject.fhirBundle.entry[0].fullUrl).toContain('resource:0');
expect(shcCert.credentialSubject.fhirBundle.entry[0].resource.resourceType).toBe('Patient');
expect(shcCert.credentialSubject.fhirBundle.entry[0].resource.name[0].text).toBe(cert2.credentialSubject.name);
expect(shcCert.credentialSubject.fhirBundle.entry[1].fullUrl).toContain('resource:1');
expect(shcCert.credentialSubject.fhirBundle.entry[1].resource.resourceType).toBe('Immunization');
expect(shcCert.credentialSubject.fhirBundle.entry[1].resource.occurrenceDateTime).toBe(cert2.evidence[0].date);
expect(shcCert.credentialSubject.fhirBundle.entry[1].resource.lotNumber).toBe(cert2.evidence[0].batch);
expect(shcCert.credentialSubject.fhirBundle.entry[1].resource.performer[0].actor.display).toBe(cert2.evidence[0].facility.name);
});
test('should throw exception when unsupported vaccine name is passed', async () => {
let cert = JSON.parse(JSON.stringify(cert2));
cert.evidence[0].vaccine = 'vacc1';
await expect(certificateToFhirJson(cert, k1PrvP8PPEM, meta))
.rejects
.toThrow('unsupported vaccine name vacc1');
});
test('should validate signed FHIR Json given public key', async () => {
let fhirCert = await certificateToFhirJson(cert2, k1PrvP8PPEM, meta);
expect(validateSignedFhirJson(fhirCert, k1PubP8PEM)).toBeTruthy();
// validate QRContent
expect(validateQRContent(fhirCert.entry[4].resource.content[1].attachment.id, k1PubP8PEM)).toBeTruthy()
});
test('should validate signed FHIR Json given ECDSA public key', async () => {
let fhirCert = await certificateToFhirJson(cert2, k1PrvP8PPEM, meta);
expect(validateSignedFhirJson(fhirCert, k1PubP8PEM)).toBeTruthy();
// validate QRContent
const qRPayload = fhirCert.entry[4].resource.content[1].attachment.id
expect(validateQRContent(qRPayload, k1PubP8PEM)).toBeTruthy();
console.log(rs.jws.JWS.readSafeJSONString(rs.b64utoutf8(qRPayload.split(".")[1])));
});
test('should invalidate signed FHIR Json with wrong public private key pair', async () => {
const dummyPublicKeyPem = '-----BEGIN PUBLIC KEY-----\nuwcD1WEYfY5N7N0ArTzPgctExtZFDmituLGzuAZf\n-----END PUBLIC KEY-----\n';
let fhirCert = await certificateToFhirJson(cert2, k1PrvP8PPEM, meta);
expect(validateSignedFhirJson(fhirCert, dummyPublicKeyPem)).toBeFalsy();
});