Last active
November 29, 2021 15:07
-
-
Save loreberti89/743bf8d5ab234f3b4f148adc3fe05f24 to your computer and use it in GitHub Desktop.
A NodeJs script wrapped for a green pass validation by translating from Verification c-19
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "ita_green_pass_validation", | |
"version": "1.0.0", | |
"main": "index.js", | |
"license": "MIT", | |
"scripts": { | |
}, | |
"dependencies": { | |
"axios": "^0.21.1", | |
"dcc-utils": "https://github.com/ministero-salute/dcc-utils", | |
"jsrsasign": "^10.4.0", | |
"jsrsasign-util": "^1.0.5", | |
"moment": "^2.29.1", | |
"node-inspect-extracted": "^1.0.8" | |
}, | |
"devDependencies": {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { DCC, Rule} = require('dcc-utils'); | |
const axios = require('axios').default; | |
var fs = require("fs"); | |
const rs = require('jsrsasign'); | |
const rsu = require('jsrsasign-util'); | |
const moment = require('moment'); | |
//const get from VERIFICA c-19 | |
const APP_MIN_VERSION = "android"; | |
const RECOVERY_CERT_START_DAY = "recovery_cert_start_day"; | |
const RECOVERY_CERT_END_DAY = "recovery_cert_end_day"; | |
const MOLECULAR_TEST_START_HOUR = "molecular_test_start_hours"; | |
const MOLECULAR_TEST_END_HOUR = "molecular_test_end_hours"; | |
const RAPID_TEST_START_HOUR = "rapid_test_start_hours"; | |
const RAPID_TEST_END_HOUR = "rapid_test_end_hours"; | |
const VACCINE_START_DAY_NOT_COMPLETE = "vaccine_start_day_not_complete"; | |
const VACCINE_END_DAY_NOT_COMPLETE = "vaccine_end_day_not_complete"; | |
const VACCINE_START_DAY_COMPLETE = "vaccine_start_day_complete"; | |
const VACCINE_END_DAY_COMPLETE = "vaccine_end_day_complete"; | |
const NOT_DETECTED = "260415000"; | |
const NOT_VALID_YET= "Not valid yet"; | |
const VALID= "Valid"; | |
const NOT_VALID= "Not valid"; | |
const NOT_GREEN_PASS= "Not a green pass"; | |
const PARTIALLY_VALID = "Valid only in Italy"; | |
const TEST_RAPID = "LP217198-3"; | |
const TEST_MOLECULAR = "LP6464-4"; | |
//values get from https://github.com/eu-digital-green-certificates/dgca-app-core-android/blob/b9ba5b3bc7b8f1c510a79d07bbaecae8a6edfd74/decoder/src/main/java/dgca/verifier/app/decoder/model/Test.kt | |
const DETECTED = "260373001"; | |
Array.prototype.last = function() { | |
return this[this.length - 1]; | |
} | |
/** tutte queste funzioni sono state prese da Verifica c-19 VerificationModel.kt */ | |
function getVaccineStartDayNotComplete(rules, vaccineType){ | |
return rules.find(it => { | |
return it.name == VACCINE_START_DAY_NOT_COMPLETE && it.type == vaccineType | |
}) | |
} | |
function getVaccineEndDayNotComplete(rules, vaccineType){ | |
return rules.find(it => { | |
return it.name == VACCINE_END_DAY_NOT_COMPLETE && it.type == vaccineType | |
}) | |
} | |
function getVaccineStartDayComplete(rules, vaccineType){ | |
return rules.find(it => { | |
return it.name == VACCINE_START_DAY_COMPLETE && it.type == vaccineType | |
}) | |
} | |
function getVaccineEndDayComplete(rules, vaccineType){ | |
return rules.find(it => { | |
return it.name == VACCINE_END_DAY_COMPLETE && it.type == vaccineType | |
}) | |
} | |
function getRecoveryCertStartDay(rules, vaccineType) { | |
return rules.find(it => { | |
return it.name == RECOVERY_CERT_START_DAY | |
}) | |
} | |
function getRecoveryCertEndDay(rules, vaccineType) { | |
return rules.find(it => { | |
return it.name == RECOVERY_CERT_END_DAY | |
}) | |
} | |
function getRapidTestStartHour(rules) { | |
return rules.find(it => { | |
return it.name == RAPID_TEST_START_HOUR | |
}) | |
} | |
function getRapidTestEndHour(rules) { | |
return rules.find(it => { | |
return it.name == RAPID_TEST_END_HOUR | |
}) | |
} | |
function getMolecularTestStartHour(rules) { | |
return rules.find(it => { | |
return it.name == MOLECULAR_TEST_START_HOUR | |
}) | |
} | |
function getMolecularTestEndHour(rules){ | |
return rules.find(it => { | |
return it.name == MOLECULAR_TEST_END_HOUR | |
}) | |
} | |
/* Function from VERIFICA-C19 */ | |
function checkTests(rules, dcc, typeOfTest){ | |
var obj = dcc.payload.t.last(); | |
var message = ""; | |
var result = false; | |
var now = moment(); | |
if (obj.tr == DETECTED) { | |
message = NOT_VALID | |
}else{ | |
try { | |
var typeOfTest = obj.tt; | |
//in app Verifica C-19 get data and parse in UTC and then parse in Local | |
var odtDateTimeOfCollection = moment.utc(obj.sc) | |
//ne devo creare due perché se poi faccio l'add, lui mi modifica la variabile. | |
var ldtDateTimeOfCollection = moment(odtDateTimeOfCollection).local() | |
var endDateLdt = ldtDateTimeOfCollection.clone(); | |
var startDate; | |
var endDate; | |
//chekc types of Test, can be MOLECULAR or RAPID | |
if(typeOfTest == TEST_MOLECULAR){ | |
startDate = ldtDateTimeOfCollection.add(parseInt(getMolecularTestStartHour(rules).value), 'hours') | |
endDate = endDateLdt.add(parseInt(getMolecularTestEndHour(rules).value), 'hours') | |
}else | |
if(typeOfTest == TEST_RAPID){ | |
startDate = ldtDateTimeOfCollection.add(parseInt(getRapidTestStartHour(rules).value), 'hours') | |
endDate = endDateLdt.add(parseInt(getRapidTestEndHour(rules).value), 'hours') | |
}else{ | |
result = false; | |
message = NOT_VALID | |
return { | |
result, | |
message | |
}; | |
} | |
if(startDate.isAfter(now)){ | |
result = false; | |
message = NOT_VALID_YET | |
}else if(now.isAfter(endDate)){ | |
result = false; | |
message = NOT_VALID | |
}else{ | |
result = true; | |
message = VALID; | |
} | |
} catch (e) { | |
result = false; | |
message = NOT_GREEN_PASS; | |
} | |
} | |
return { | |
result, | |
message | |
}; | |
} | |
/* Function from VERIFICA-C19 */ | |
function checkRecoveryStatements(dcc){ | |
var obj = dcc.payload.r.last(); | |
var now = moment(); | |
var message = ""; | |
var result = false; | |
try { | |
var startDate = moment(obj.df); | |
var endDate =moment(obj.du); | |
if(startDate.isAfter(now)){ | |
message = NOT_VALID_YET; | |
}else if(now.isAfter(endDate)){ | |
message = NOT_VALID; | |
}else{ | |
result = true; | |
message = VALID; | |
}; | |
} catch (e) { | |
result = false; | |
message = NOT_VALID; | |
} | |
return { | |
result, | |
message | |
}; | |
} | |
/* Function from VERIFICA-C19 */ | |
function validateRules(rules, dcc) { | |
var obj = dcc.payload.v.last(); | |
var vaccineType = obj.mp; | |
var doseNumber = obj.dn; | |
var dateOfVaccination = obj.dt; | |
var totalSeriesOfDoses = obj.sd; | |
var rule = getVaccineEndDayComplete(rules, vaccineType); | |
var now = moment.now(); | |
var message = ""; | |
var result = false; | |
if(rule && rule != null){ | |
try{ | |
if(doseNumber < totalSeriesOfDoses){ | |
var startDate = moment(dateOfVaccination).add(parseInt(getVaccineStartDayNotComplete(rules, vaccineType).value), 'days'); | |
var endDate = moment(dateOfVaccination).add(parseInt(getVaccineEndDayNotComplete(rules, vaccineType).value), 'days'); | |
if(startDate.isAfter(now)){ | |
message = NOT_VALID_YET; | |
}else if(now.isAfter(endDate)){ | |
message = NOT_VALID; | |
}else{ | |
result = true; | |
message = PARTIALLY_VALID; | |
} | |
}else | |
if(doseNumber >= totalSeriesOfDoses){ | |
var startDate = moment(dateOfVaccination).add(parseInt(getVaccineStartDayComplete(rules, vaccineType).value), 'days'); | |
var endDate = moment(dateOfVaccination).add(parseInt(getVaccineEndDayComplete(rules, vaccineType).value), 'days'); | |
if(startDate.isAfter(now)){ | |
message = NOT_VALID_YET; | |
}else{ | |
result = true; | |
message = VALID; | |
} | |
}else{ | |
message = NOT_VALID | |
} | |
}catch (e){ | |
message = NOT_VALID | |
} | |
}else{ | |
message = NOT_VALID | |
} | |
return { | |
result, | |
message | |
}; | |
} | |
function exitAndValid(message){ | |
console.log(message); | |
process.exit(0); | |
} | |
const main = async function () { | |
try{ | |
//first argument must be a "greenpass raw" | |
var code = process.argv.slice(2)[0]; | |
//second arguments must be a json of certificates i get this certificates from https://get.dgc.gov.it/v1/dgc/signercertificate/update | |
//from example how get all see at https://github.com/ministero-salute/dcc-utils/issues/1 | |
var certs = JSON.parse(fs.readFileSync(process.argv.slice(2)[1])); | |
//third arguments mustbe a rules that i get from https://get.dgc.gov.it/v1/dgc/settings | |
var rules = JSON.parse(fs.readFileSync(process.argv.slice(2)[2])); | |
//value sets was in https://github.com/ministero-salute/dcc-utils but for now I can't figured out how should I use this for italy and so, I use rules from https://get.dgc.gov.it/v1/dgc/settings and validate rules as VERIFICA C-19 that not use certLogic | |
// var valueSets = JSON.parse(fs.readFileSync(process.argv.slice(2)[3])); | |
var verified; | |
const dcc = await DCC.fromRaw(code); | |
let resp = null; | |
var certificateValid = false; | |
var messageError; | |
for(var i = 0; i < certs.length; i++){ | |
try { | |
const verifier = rs.KEYUTIL.getKey(certs[i]).getPublicKeyXYHex(); | |
verified = await dcc.checkSignature(verifier); | |
if(verified){ | |
certificateValid = true; | |
try{ | |
//the green pass can be different types | |
//if there is a V (vaccinated) I should check Vaccination with Rules, function get from VERIFICA c-19 name VerificationViewModel->checkVaccinations | |
if(dcc.payload.v){ | |
const isValidRules = validateRules(rules, dcc) | |
if (isValidRules.result === false) { | |
certificateValid = false; | |
messageError = isValidRules.message; | |
break; | |
}else{ | |
exitAndValid(isValidRules.message); | |
break; | |
} | |
} | |
//If there is a T (test) I should check if Test is valid, function get from VERIFICA c-19 name VerificationViewModel->checkTests | |
if(dcc.payload.t){ | |
const isValidTest = checkTests(rules, dcc) | |
if (isValidTest.result === false) { | |
certificateValid = false; | |
messageError = isValidTest.message; | |
break; | |
}else{ | |
exitAndValid(isValidTest.message); | |
break; | |
} | |
} | |
//If there is R (recovery) I should check recoveryStatement , function get from VERIFICA c-19 name VerificationViewModel->checkRecoveryStatements | |
if(dcc.payload.r){ | |
const isValidStatement = checkRecoveryStatements(dcc) | |
if (isValidStatement.result === false) { | |
certificateValid = false; | |
messageError = isValidStatement.message; | |
break; | |
}else{ | |
exitAndValid(isValidStatement.message); | |
break; | |
} | |
} | |
}catch (e){ | |
console.log(e); | |
certificateValid = false; | |
messageError = e.message; | |
break; | |
} | |
} | |
} | |
catch(e) { | |
messageError = "This certificate has not a valid Signature"; | |
} | |
} | |
}catch (e){ | |
throw new Error(e.message) | |
process.exit(1); | |
return false; | |
} | |
try{ | |
if(certificateValid == false){ | |
console.log(messageError); | |
throw new Error(messageError) | |
}else{ | |
process.exit(0) | |
}; | |
}catch (e){ | |
process.exit(1); | |
} | |
}; | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment