first working version
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Verifier from '../../../utils/dgcVerifier/Verifier';
|
||||
import Verifier from '../../utils/dgcVerifier/Verifier';
|
||||
let verifier:Verifier;
|
||||
Verifier.instanceVerifier().then((ver)=>verifier=ver).catch(console.error);
|
||||
Verifier.instanceVerifier().then((ver:Verifier)=>verifier=ver).catch(console.error);
|
||||
|
||||
export const get = (req: Request, res: Response):void => {
|
||||
const cert = req.body;
|
||||
const result = verifier.checkCertificate(cert);
|
||||
res.status(200).send({'data':result});
|
||||
export const get = async (req: Request, res: Response):Promise<void> => {
|
||||
const cert = req.body['key'];
|
||||
try {
|
||||
const result = await verifier.checkCertificate(cert);
|
||||
res.status(200).send(result);
|
||||
} catch (error) {
|
||||
res.status(200).send({message:'unsigned certificate',error});
|
||||
}
|
||||
};
|
||||
@@ -10,7 +10,7 @@ export default class GreenApi implements API {
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
setupApi():void {
|
||||
setupApi(): void {
|
||||
this.router.post('/green',middleware.canGet,controller.get);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export const canGet = (req: Request, res: Response, next: NextFunction):void => {
|
||||
|
||||
return next();
|
||||
};
|
||||
@@ -5,9 +5,9 @@ export const setupApis = (application: Express):void => {
|
||||
const router = Router();
|
||||
const exampleApi = new GreenApi(router);
|
||||
|
||||
exampleApi.setupApi();
|
||||
|
||||
exampleApi.setupApi();
|
||||
application.use('/api', router);
|
||||
console.log('inited');
|
||||
};
|
||||
|
||||
export interface API {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { setupApis } from './api';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
import './api';
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded());
|
||||
setupApis(app);
|
||||
|
||||
export default app;
|
||||
@@ -1,5 +1,4 @@
|
||||
import app from './app';
|
||||
|
||||
const server = app.listen(app.get('port'), () => {
|
||||
console.log(
|
||||
'App is running at http://localhost:%d in %s mode',
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
import { response } from 'express';
|
||||
import fs from 'fs/promises';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
export class CertificateDownloader{
|
||||
// static instance: CertificateDownloader;
|
||||
private readonly baseUrl = 'https://get.dgc.gov.it';
|
||||
private readonly updateApi = '/v1/dgc/signercertificate/update'
|
||||
private readonly statusApi = '/v1/dgc/signercertificate/status'
|
||||
private readonly keyStorage = './cerificate_collection.json';
|
||||
private readonly timeSpan = 86400000;
|
||||
private cerficateCollection:string[] = [];
|
||||
private currentValidKids = [];
|
||||
// private readonly timeSpan = 86400000;
|
||||
private readonly timeSpan = 1;
|
||||
private cerficateCollection:unknown = {};
|
||||
private currentValidKids:string[] = [];
|
||||
|
||||
public constructor(){
|
||||
this.getCertificates();
|
||||
}
|
||||
|
||||
public async getCertificates(): Promise<string[]> {
|
||||
const file = await fs.open(this.keyStorage,'r');
|
||||
const data = await file.readFile();
|
||||
const savedData = JSON.parse( data.toString() || '{}');
|
||||
if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){
|
||||
this.getAllCertificate()
|
||||
.then(() => { console.log('could not read the certificates from the local file'); return this.cerficateCollection; })
|
||||
.catch(console.error);
|
||||
public async getCertificates(): Promise<unknown> {
|
||||
let data = '{}';
|
||||
try {
|
||||
const file = await fs.open(this.keyStorage,'r');
|
||||
data = (await file.readFile()).toString('utf-8');
|
||||
await file.close();
|
||||
const savedData = JSON.parse( data || '{}');
|
||||
// if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){
|
||||
// await this.getAllCertificate();
|
||||
// } else {
|
||||
this.cerficateCollection = savedData.certificates;
|
||||
// }
|
||||
return this.cerficateCollection;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if(error.errno == -2){
|
||||
await fs.writeFile(this.keyStorage,'{}');
|
||||
} else {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
console.log('cerficates collection is valid loading from local source');
|
||||
this.cerficateCollection = savedData.certificates;
|
||||
return this.cerficateCollection;
|
||||
}
|
||||
|
||||
// public static getCertificateDownloader():CertificateDownloader{
|
||||
@@ -34,45 +43,54 @@ export class CertificateDownloader{
|
||||
// return CertificateDownloader.instance;
|
||||
// }
|
||||
|
||||
async getAllCertificate(): Promise<void> {
|
||||
this.cerficateCollection = {};
|
||||
const response:AxiosResponse<JSON> = (await axios.get('https://raw.githubusercontent.com/lovasoa/sanipasse/master/src/assets/Digital_Green_Certificate_Signing_Keys.json'));
|
||||
if(response.status == 200){
|
||||
console.log(response.data);
|
||||
this.cerficateCollection = response.data;
|
||||
console.log(response);
|
||||
const lastupdateDate = Date.now();
|
||||
const file = await fs.open(this.keyStorage,'w');
|
||||
file.writeFile(JSON.stringify({'certificates':this.cerficateCollection, lastupdateDate}));
|
||||
console.log(this.cerficateCollection);
|
||||
await file.close();
|
||||
}else{
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
}
|
||||
|
||||
// async getAllCertificate(): Promise<void> {
|
||||
// this.cerficateCollection = {};
|
||||
// const response = (await fetch('https://raw.githubusercontent.com/lovasoa/sanipasse/master/src/assets/Digital_Green_Certificate_Signing_Keys.json'));
|
||||
// if(response.ok){
|
||||
// this.cerficateCollection = await response.json();
|
||||
// console.log(response);
|
||||
// const lastupdateDate = Date.now();
|
||||
// const file = await fs.open(this.keyStorage,'rw');
|
||||
// file.writeFile(JSON.stringify({'certificates':this.cerficateCollection, lastupdateDate}));
|
||||
// }else{
|
||||
// throw new Error(response.statusText);
|
||||
// let exit = false;
|
||||
// let headers = {};
|
||||
// this.cerficateCollection = [];
|
||||
// while(!exit){
|
||||
// // const response = await fetch(this.baseUrl+this.updateApi,{headers});
|
||||
// const response:AxiosResponse = await axios.get(this.baseUrl+this.updateApi,{headers});
|
||||
// // console.log(response.headers);
|
||||
// headers = {'X-RESUME-TOKEN': response.headers['x-resume-token']};
|
||||
// const currentKid:string = response.headers['x-kid'];
|
||||
// if(this.currentValidKids.includes(currentKid)){
|
||||
// // console.log('=========AGGIUNG===========');
|
||||
// const cert = `-----BEGIN CERTIFICATE-----${response.data}-----END CERTIFICATE-----`;
|
||||
// // console.log(cert);
|
||||
// this.cerficateCollection.push(cert);
|
||||
// }
|
||||
// exit = (response.status !== 200);
|
||||
// }
|
||||
// const lastupdateDate = Date.now();
|
||||
// const file = await fs.open(this.keyStorage,'w');
|
||||
// file.writeFile(JSON.stringify({'certificates':this.cerficateCollection, lastupdateDate}));
|
||||
// console.log(this.cerficateCollection);
|
||||
// await file.close();
|
||||
// }
|
||||
|
||||
async getAllCertificate(): Promise<void> {
|
||||
let exit = false;
|
||||
let headers = {};
|
||||
this.cerficateCollection = [];
|
||||
while(!exit){
|
||||
const response = await fetch(this.baseUrl+this.updateApi,{headers});
|
||||
headers = {'X-RESUME-TOKEN': response.headers.get('X-RESUME-TOKEN')};
|
||||
const currentKid:string = response.headers.get('X-KID') || '';
|
||||
if(this.currentValidKids.includes(currentKid as never)){
|
||||
const cert = await response.text();
|
||||
this.cerficateCollection.push('-----BEGIN CERTIFICATE-----\n' + cert + '-----END CERTIFICATE-----');
|
||||
}
|
||||
exit = (response.status !== 200);
|
||||
}
|
||||
const lastupdateDate = Date.now();
|
||||
const file = await fs.open(this.keyStorage,'rw');
|
||||
file.writeFile(JSON.stringify({'certificates':this.cerficateCollection, lastupdateDate}));
|
||||
}
|
||||
|
||||
async updateKids(): Promise<void> {
|
||||
try {
|
||||
const resp = await fetch(this.baseUrl+this.statusApi);
|
||||
this.currentValidKids = await resp.json();
|
||||
} catch (error) {
|
||||
console.log('could not get keyChild ', error);
|
||||
}
|
||||
}
|
||||
// async updateKids(): Promise<void> {
|
||||
// try {
|
||||
// const resp = await axios.get(this.baseUrl+this.statusApi);
|
||||
// this.currentValidKids = await resp.data as string[];
|
||||
// } catch (error) {
|
||||
// console.log('could not get keyChild ', error);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,34 +1,49 @@
|
||||
import fs from 'fs/promises';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
export class RuleDownloader {
|
||||
static instance: RuleDownloader;
|
||||
private readonly baseUrl = 'https://get.dgc.gov.it';
|
||||
private readonly timeSpan = 86400000;
|
||||
private readonly keyStorage = 'rules'
|
||||
// private readonly timeSpan = 1;
|
||||
private readonly keyStorage = 'rules.json'
|
||||
// private readonly timeSpan = 1000;
|
||||
public rules:unknown = {}
|
||||
public rules:unknown[] = []
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
constructor(){
|
||||
this.getRules();
|
||||
}
|
||||
constructor(){}
|
||||
|
||||
public getRules(): unknown {
|
||||
const savedData = JSON.parse(localStorage.getItem(this.keyStorage) || '{}');
|
||||
if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){
|
||||
this.getSettings()
|
||||
.then(() => { console.log('could not read the certificates from the local file'); return this.rules; })
|
||||
.catch(console.error);
|
||||
public async getRules(): Promise<unknown[]> {
|
||||
let data = '{}';
|
||||
try {
|
||||
const file = await fs.open(this.keyStorage,'r');
|
||||
data = (await file.readFile()).toString('utf-8');
|
||||
await file.close();
|
||||
const savedData = JSON.parse(data);
|
||||
if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){
|
||||
await this.getSettings();
|
||||
} else {
|
||||
this.rules = savedData.rules;
|
||||
}
|
||||
return this.rules;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if(error.code == 'ENONET'){
|
||||
await fs.writeFile(this.keyStorage,'{}');
|
||||
} else {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
console.log('cerficates collection is valid loading from local source');
|
||||
// console.log(dataRead.certificates);
|
||||
this.rules = savedData.certificates;
|
||||
return this.rules;
|
||||
}
|
||||
|
||||
private async getSettings(): Promise<unknown>{
|
||||
const response = await fetch(`${this.baseUrl}/v1/dgc/settings`);
|
||||
const jsonData = await response.json();
|
||||
private async getSettings(): Promise<unknown[]>{
|
||||
const response:AxiosResponse<unknown[]> = await axios.get(`${this.baseUrl}/v1/dgc/settings`);
|
||||
const jsonData = response.data;
|
||||
// this.rules = Rule.fromJSON(jsonData,{valueSets, validationClock:new Date().toISOString(),})
|
||||
this.rules = jsonData;
|
||||
localStorage.setItem(this.keyStorage, JSON.stringify({rules:this.rules,lastupdateDate:Date.now()}));
|
||||
// localStorage.setItem(this.keyStorage, JSON.stringify({rules:this.rules,lastupdateDate:Date.now()}));
|
||||
const file = await fs.open(this.keyStorage,'w');
|
||||
file.writeFile(JSON.stringify({rules:this.rules,lastupdateDate:Date.now()}));
|
||||
await file.close();
|
||||
return jsonData;
|
||||
}
|
||||
}
|
||||
129
src/utils/dgcVerifier/VaccineVerifier.ts
Normal file
129
src/utils/dgcVerifier/VaccineVerifier.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import {DCC} from 'dcc-utils';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
|
||||
interface checkResult {
|
||||
valid: boolean;
|
||||
message: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class VaccineVerifier {
|
||||
private readonly vaccineStartDayNotComplete = 'vaccine_start_day_not_complete';
|
||||
private readonly vaccineEndDayNotComplete = 'vaccine_end_day_not_complete';
|
||||
private readonly vaccineStartDayComplete = 'vaccine_start_day_complete';
|
||||
private readonly vaccineEndDayComplete = 'vaccine_end_day_complete';
|
||||
private readonly recoveryCertStartDay = 'recovery_cert_start_day';
|
||||
private readonly recoveryCertEndDay = 'recovery_cert_end_day';
|
||||
private readonly rapidTestStartHour = 'rapid_test_start_hours';
|
||||
private readonly rapidTestEndHour = 'rapid_test_end_hours';
|
||||
private readonly molecularTestStartHour = 'molecular_test_start_hours';
|
||||
private readonly molecularTestEndHour = 'molecular_test_end_hours';
|
||||
private readonly positiveTest = '260373001';
|
||||
private readonly negativeTest = '260415000';
|
||||
private readonly rapidTest = 'LP217198-3'; // RAT, Rapid Antigen Test
|
||||
private readonly molecularTest = 'LP6464-4'; // NAAT, Nucleic Acid Amplification Test
|
||||
|
||||
settings=[];
|
||||
certTypes=['r','v','t',]
|
||||
|
||||
private checkVaccine = (payload:any):checkResult => {
|
||||
const inoculationDate = dayjs(payload.dt);
|
||||
const validRulesSet = this.getRulesSet(payload.mp);
|
||||
const vaccineDiff = payload.sd - payload.dn;
|
||||
const baseRuleIndex = validRulesSet.findIndex((elem:any)=>{ return elem.name == this.vaccineEndDayComplete;});
|
||||
if( baseRuleIndex == -1)
|
||||
return {valid:false, message:'Invaild set of rules check with operator', type:'vaccine'};
|
||||
if(vaccineDiff <= 0){
|
||||
return this.getLogicValidityDays(validRulesSet, this.vaccineStartDayComplete, this.vaccineEndDayComplete,inoculationDate);
|
||||
} else {
|
||||
return this.getLogicValidityDays(validRulesSet, this.vaccineStartDayNotComplete, this.vaccineEndDayNotComplete,inoculationDate);
|
||||
}
|
||||
}
|
||||
|
||||
private checkRecovery = (payload:any):checkResult => {
|
||||
const now = dayjs();
|
||||
const dateFrom = dayjs(payload.df);
|
||||
const dateEnd = dayjs(payload.du);
|
||||
if(now.isAfter(dateFrom) && now.isBefore(dateEnd)){
|
||||
return{valid:true, message:'Certificate is valid', type:'recovery'};
|
||||
}
|
||||
return {valid:false, message:'toimplement', type:'recovery'};
|
||||
}
|
||||
|
||||
private checkTest = (payload:any):checkResult => {
|
||||
const validRulesSet = this.getRulesSet('GENERIC');
|
||||
const testType = payload.tt;
|
||||
if(payload.tr === this.positiveTest)
|
||||
return {valid:false, message:'The test detected the virus',type:'test'};
|
||||
const collectionDateTime = dayjs.tz(payload.sc,'UTC').tz(dayjs.tz.guess());
|
||||
if(testType == this.rapidTest){
|
||||
return this.getLogicValidityHours(validRulesSet,this.rapidTestStartHour,this.rapidTestEndHour,collectionDateTime);
|
||||
}
|
||||
if(testType == this.molecularTest){
|
||||
return this.getLogicValidityHours(validRulesSet,this.molecularTestStartHour,this.molecularTestEndHour,collectionDateTime);
|
||||
}
|
||||
return {valid:false, message:'unknown test type',type:'test'};
|
||||
}
|
||||
|
||||
private functionSelector = {
|
||||
'v':this.checkVaccine,
|
||||
'r':this.checkRecovery,
|
||||
't':this.checkTest
|
||||
}
|
||||
|
||||
constructor(settings:unknown[]) {
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public checkCertifcate(pass:DCC):checkResult {
|
||||
console.log(pass.payload);
|
||||
const payloadAndType = this.getPayloadAndType(pass);
|
||||
const result: checkResult = this.functionSelector[payloadAndType.key](payloadAndType.payload);
|
||||
return result;
|
||||
}
|
||||
|
||||
private getPayloadAndType(pass:DCC): {key:string,payload:unknown}{
|
||||
const result:{key:string,payload:unknown} = {key:'',payload:[]};
|
||||
this.certTypes.forEach((key:string) => {
|
||||
if(pass.payload[key] != undefined){
|
||||
if((pass.payload[key] as unknown[]).length != 0){
|
||||
result.key =(key);
|
||||
result.payload = (pass.payload[key][pass.payload[key].length -1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private getRulesSet(type:string): unknown[]{
|
||||
return this.settings.filter((rule:unknown)=>{
|
||||
return rule['type'] = type;
|
||||
});
|
||||
}
|
||||
|
||||
private getLogicValidityDays(validRulesSet:unknown[],startKey:string, endKey:string, inoculationDate: dayjs.Dayjs): checkResult {
|
||||
const now = dayjs();
|
||||
const ruleStart = validRulesSet.find((elem:any)=>{return elem.name == startKey;});
|
||||
const ruleEnd = validRulesSet.find((elem:any)=>{return elem.name == endKey;});
|
||||
const startValidity = inoculationDate.add(parseInt(ruleStart['value']),'days');
|
||||
const endValidity = inoculationDate.add(parseInt(ruleEnd['value']),'days');
|
||||
if(startValidity.isAfter(now)) return {valid:false, message:'Certificate not yet valid', type:'vaccine'};
|
||||
if(now.isAfter(endValidity)) return {valid:false, message:'Certificate not more valid', type:'vaccine'};
|
||||
return {valid:true, message:'Certificate is valid', type:'vaccine'};
|
||||
}
|
||||
|
||||
private getLogicValidityHours(validRulesSet:unknown[],startKey:string, endKey:string, inoculationDate: dayjs.Dayjs): checkResult {
|
||||
const now = dayjs();
|
||||
const ruleStart = validRulesSet.find((elem:any)=>{return elem.name == startKey;});
|
||||
const ruleEnd = validRulesSet.find((elem:any)=>{return elem.name == endKey;});
|
||||
const startValidity = inoculationDate.add(parseInt(ruleStart['value']),'hours');
|
||||
const endValidity = inoculationDate.add(parseInt(ruleEnd['value']),'hours');
|
||||
if(startValidity.isAfter(now)) return {valid:false, message:'Certificate not yet valid', type:'test'};
|
||||
if(now.isAfter(endValidity)) return {valid:false, message:'Certificate not more valid', type:'test'};
|
||||
return {valid:true, message:'Certificate is valid', type:'test'};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
import { CertificateDownloader } from './CertificateDownloader';
|
||||
import { RuleDownloader } from './RuleDownloader';
|
||||
import { VaccineVerifier } from './VaccineVerifier';
|
||||
import {DCC} from 'dcc-utils';
|
||||
import jsrsasign from 'jsrsasign';
|
||||
|
||||
export default class Verifier {
|
||||
static instance: Verifier|undefined = undefined;
|
||||
private certDownloader: CertificateDownloader;
|
||||
private ruleDownloader: RuleDownloader;
|
||||
private certificateList: string[] = [];
|
||||
|
||||
private constructor(){
|
||||
this.certDownloader = new CertificateDownloader();
|
||||
this.ruleDownloader = new RuleDownloader();
|
||||
@@ -17,22 +15,20 @@ export default class Verifier {
|
||||
public static async instanceVerifier(): Promise<Verifier>{
|
||||
if (Verifier.instance == undefined){
|
||||
Verifier.instance = new Verifier();
|
||||
Verifier.instance.certificateList = await Verifier.instance.certDownloader.getCertificates();
|
||||
await Verifier.instance.certDownloader.getCertificates();
|
||||
await Verifier.instance.ruleDownloader.getRules();
|
||||
|
||||
}
|
||||
return Verifier.instance;
|
||||
}
|
||||
|
||||
async checkCertificate(certificate:string): Promise<unknown>{
|
||||
console.log(certificate);
|
||||
const dcc = await DCC.fromRaw(certificate);
|
||||
let result: unknown;
|
||||
this.certificateList.forEach(async (cert: string) => {
|
||||
const verifier = jsrsasign.KEYUTIL.getKey(cert);
|
||||
if (typeof verifier == typeof jsrsasign.KJUR.crypto.ECDSA ){
|
||||
const xyCoord = (verifier as jsrsasign.KJUR.crypto.ECDSA).getPublicKeyXYHex();
|
||||
result = await dcc.checkSignature(xyCoord);
|
||||
}
|
||||
});
|
||||
console.log(result);
|
||||
let result = await dcc.checkSignatureWithKeysList(await this.certDownloader.getCertificates());
|
||||
const vaccineVerifier = new VaccineVerifier(await this.ruleDownloader.getRules());
|
||||
result = {signature: result, valid:vaccineVerifier.checkCertifcate(dcc)};
|
||||
console.log(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user