first working version

This commit is contained in:
Gianmarco Pettinato 2021-09-24 15:21:24 +02:00
parent d8a836aa90
commit 0c5d06cfc1
15 changed files with 3650 additions and 133 deletions

3294
cerificate_collection.json Normal file

File diff suppressed because it is too large Load Diff

119
package-lock.json generated
View File

@ -9,7 +9,9 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^0.21.4",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"dayjs": "^1.10.7",
"dcc-utils": "^0.2.0", "dcc-utils": "^0.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"jsrsasign": "^10.4.0", "jsrsasign": "^10.4.0",
@ -2533,6 +2535,19 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/@react-native-community/cli-tools/node_modules/node-fetch": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
"optional": true,
"peer": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/@react-native-community/cli-types": { "node_modules/@react-native-community/cli-types": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-6.0.0.tgz", "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-6.0.0.tgz",
@ -3653,6 +3668,14 @@
"node": ">= 4.5.0" "node": ">= 4.5.0"
} }
}, },
"node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/b64-lite": { "node_modules/b64-lite": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.4.0.tgz", "resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.4.0.tgz",
@ -4720,9 +4743,7 @@
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.10.7", "version": "1.10.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
"optional": true,
"peer": true
}, },
"node_modules/dcc-utils": { "node_modules/dcc-utils": {
"version": "0.2.0", "version": "0.2.0",
@ -6098,6 +6119,25 @@
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
"dev": true "dev": true
}, },
"node_modules/follow-redirects": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
"integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-in": { "node_modules/for-in": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -8182,6 +8222,19 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node_modules/metro/node_modules/node-fetch": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
"optional": true,
"peer": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/metro/node_modules/rimraf": { "node_modules/metro/node_modules/rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -8400,19 +8453,6 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/node-fetch": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
"optional": true,
"peer": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/node-hkdf-sync": { "node_modules/node-hkdf-sync": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz",
@ -13961,6 +14001,16 @@
"integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==",
"optional": true, "optional": true,
"peer": true "peer": true
},
"node-fetch": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
"optional": true,
"peer": true,
"requires": {
"whatwg-url": "^5.0.0"
}
} }
} }
}, },
@ -14723,6 +14773,14 @@
"optional": true, "optional": true,
"peer": true "peer": true
}, },
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
},
"b64-lite": { "b64-lite": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.4.0.tgz", "resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.4.0.tgz",
@ -15569,9 +15627,7 @@
"dayjs": { "dayjs": {
"version": "1.10.7", "version": "1.10.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
"optional": true,
"peer": true
}, },
"dcc-utils": { "dcc-utils": {
"version": "0.2.0", "version": "0.2.0",
@ -16681,6 +16737,11 @@
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
"dev": true "dev": true
}, },
"follow-redirects": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
"integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g=="
},
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -18073,6 +18134,16 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node-fetch": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
"optional": true,
"peer": true,
"requires": {
"whatwg-url": "^5.0.0"
}
},
"rimraf": { "rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -18559,16 +18630,6 @@
"optional": true, "optional": true,
"peer": true "peer": true
}, },
"node-fetch": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
"optional": true,
"peer": true,
"requires": {
"whatwg-url": "^5.0.0"
}
},
"node-hkdf-sync": { "node-hkdf-sync": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz",

View File

@ -9,7 +9,9 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^0.21.4",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"dayjs": "^1.10.7",
"dcc-utils": "^0.2.0", "dcc-utils": "^0.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"jsrsasign": "^10.4.0", "jsrsasign": "^10.4.0",

1
rules.json Normal file
View File

@ -0,0 +1 @@
{"rules":[{"name":"vaccine_end_day_complete","type":"EU/1/20/1525","value":"365"},{"name":"vaccine_start_day_complete","type":"EU/1/20/1525","value":"15"},{"name":"vaccine_end_day_not_complete","type":"EU/1/20/1525","value":"365"},{"name":"vaccine_start_day_not_complete","type":"EU/1/20/1525","value":"15"},{"name":"vaccine_end_day_complete","type":"EU/1/21/1529","value":"365"},{"name":"vaccine_start_day_complete","type":"EU/1/21/1529","value":"0"},{"name":"vaccine_end_day_not_complete","type":"EU/1/21/1529","value":"84"},{"name":"vaccine_start_day_not_complete","type":"EU/1/21/1529","value":"15"},{"name":"vaccine_end_day_complete","type":"EU/1/20/1507","value":"365"},{"name":"vaccine_start_day_complete","type":"EU/1/20/1507","value":"0"},{"name":"vaccine_end_day_not_complete","type":"EU/1/20/1507","value":"42"},{"name":"vaccine_start_day_not_complete","type":"EU/1/20/1507","value":"15"},{"name":"vaccine_end_day_complete","type":"EU/1/20/1528","value":"365"},{"name":"vaccine_start_day_complete","type":"EU/1/20/1528","value":"0"},{"name":"vaccine_end_day_not_complete","type":"EU/1/20/1528","value":"42"},{"name":"vaccine_start_day_not_complete","type":"EU/1/20/1528","value":"15"},{"name":"rapid_test_start_hours","type":"GENERIC","value":"0"},{"name":"rapid_test_end_hours","type":"GENERIC","value":"48"},{"name":"molecular_test_start_hours","type":"GENERIC","value":"0"},{"name":"molecular_test_end_hours","type":"GENERIC","value":"48"},{"name":"recovery_cert_start_day","type":"GENERIC","value":"0"},{"name":"recovery_cert_end_day","type":"GENERIC","value":"180"},{"name":"ios","type":"APP_MIN_VERSION","value":"1.1.0"},{"name":"android","type":"APP_MIN_VERSION","value":"1.1.0"}],"lastupdateDate":1632418898529}

View File

@ -1,10 +1,14 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import Verifier from '../../../utils/dgcVerifier/Verifier'; import Verifier from '../../utils/dgcVerifier/Verifier';
let verifier: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 => { export const get = async (req: Request, res: Response):Promise<void> => {
const cert = req.body; const cert = req.body['key'];
const result = verifier.checkCertificate(cert); try {
res.status(200).send({'data':result}); const result = await verifier.checkCertificate(cert);
res.status(200).send(result);
} catch (error) {
res.status(200).send({message:'unsigned certificate',error});
}
}; };

View File

@ -10,7 +10,7 @@ export default class GreenApi implements API {
this.router = router; this.router = router;
} }
setupApi():void { setupApi(): void {
this.router.post('/green',middleware.canGet,controller.get); this.router.post('/green',middleware.canGet,controller.get);
} }
} }

View File

@ -1,6 +1,5 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
export const canGet = (req: Request, res: Response, next: NextFunction):void => { export const canGet = (req: Request, res: Response, next: NextFunction):void => {
return next(); return next();
}; };

View File

@ -6,8 +6,8 @@ export const setupApis = (application: Express):void => {
const exampleApi = new GreenApi(router); const exampleApi = new GreenApi(router);
exampleApi.setupApi(); exampleApi.setupApi();
application.use('/api', router); application.use('/api', router);
console.log('inited');
}; };
export interface API { export interface API {

View File

@ -1,12 +1,11 @@
import { setupApis } from './api';
import express from 'express'; import express from 'express';
import bodyParser from 'body-parser';
const app = express(); const app = express();
app.set('port', process.env.PORT || 3000); app.set('port', process.env.PORT || 3000);
app.use(bodyParser.json()); app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true })); app.use(express.urlencoded());
setupApis(app);
import './api';
export default app; export default app;

View File

@ -1,5 +1,4 @@
import app from './app'; import app from './app';
const server = app.listen(app.get('port'), () => { const server = app.listen(app.get('port'), () => {
console.log( console.log(
'App is running at http://localhost:%d in %s mode', 'App is running at http://localhost:%d in %s mode',

View File

@ -1,30 +1,39 @@
import { response } from 'express';
import fs from 'fs/promises'; import fs from 'fs/promises';
import axios, { AxiosResponse } from 'axios';
export class CertificateDownloader{ export class CertificateDownloader{
// static instance: CertificateDownloader; // static instance: CertificateDownloader;
private readonly baseUrl = 'https://get.dgc.gov.it'; private readonly baseUrl = 'https://get.dgc.gov.it';
private readonly updateApi = '/v1/dgc/signercertificate/update' private readonly updateApi = '/v1/dgc/signercertificate/update'
private readonly statusApi = '/v1/dgc/signercertificate/status' private readonly statusApi = '/v1/dgc/signercertificate/status'
private readonly keyStorage = './cerificate_collection.json'; private readonly keyStorage = './cerificate_collection.json';
private readonly timeSpan = 86400000; // private readonly timeSpan = 86400000;
private cerficateCollection:string[] = []; private readonly timeSpan = 1;
private currentValidKids = []; private cerficateCollection:unknown = {};
private currentValidKids:string[] = [];
public constructor(){ public async getCertificates(): Promise<unknown> {
this.getCertificates(); let data = '{}';
} try {
const file = await fs.open(this.keyStorage,'r');
public async getCertificates(): Promise<string[]> { data = (await file.readFile()).toString('utf-8');
const file = await fs.open(this.keyStorage,'r'); await file.close();
const data = await file.readFile(); const savedData = JSON.parse( data || '{}');
const savedData = JSON.parse( data.toString() || '{}'); // if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){
if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){ // await this.getAllCertificate();
this.getAllCertificate() // } else {
.then(() => { console.log('could not read the certificates from the local file'); return this.cerficateCollection; }) this.cerficateCollection = savedData.certificates;
.catch(console.error); // }
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{ // public static getCertificateDownloader():CertificateDownloader{
@ -34,45 +43,54 @@ export class CertificateDownloader{
// return CertificateDownloader.instance; // 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> { // async getAllCertificate(): Promise<void> {
// this.cerficateCollection = {}; // let exit = false;
// const response = (await fetch('https://raw.githubusercontent.com/lovasoa/sanipasse/master/src/assets/Digital_Green_Certificate_Signing_Keys.json')); // let headers = {};
// if(response.ok){ // this.cerficateCollection = [];
// this.cerficateCollection = await response.json(); // while(!exit){
// console.log(response); // // const response = await fetch(this.baseUrl+this.updateApi,{headers});
// const lastupdateDate = Date.now(); // const response:AxiosResponse = await axios.get(this.baseUrl+this.updateApi,{headers});
// const file = await fs.open(this.keyStorage,'rw'); // // console.log(response.headers);
// file.writeFile(JSON.stringify({'certificates':this.cerficateCollection, lastupdateDate})); // headers = {'X-RESUME-TOKEN': response.headers['x-resume-token']};
// }else{ // const currentKid:string = response.headers['x-kid'];
// throw new Error(response.statusText); // 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> { // async updateKids(): Promise<void> {
let exit = false; // try {
let headers = {}; // const resp = await axios.get(this.baseUrl+this.statusApi);
this.cerficateCollection = []; // this.currentValidKids = await resp.data as string[];
while(!exit){ // } catch (error) {
const response = await fetch(this.baseUrl+this.updateApi,{headers}); // console.log('could not get keyChild ', error);
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);
}
}
} }

View File

@ -1,34 +1,49 @@
import fs from 'fs/promises';
import axios, { AxiosResponse } from 'axios';
export class RuleDownloader { export class RuleDownloader {
static instance: RuleDownloader; static instance: RuleDownloader;
private readonly baseUrl = 'https://get.dgc.gov.it'; private readonly baseUrl = 'https://get.dgc.gov.it';
private readonly timeSpan = 86400000; private readonly timeSpan = 86400000;
private readonly keyStorage = 'rules' // private readonly timeSpan = 1;
private readonly keyStorage = 'rules.json'
// private readonly timeSpan = 1000; // private readonly timeSpan = 1000;
public rules:unknown = {} public rules:unknown[] = []
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
constructor(){ constructor(){}
this.getRules();
}
public getRules(): unknown { public async getRules(): Promise<unknown[]> {
const savedData = JSON.parse(localStorage.getItem(this.keyStorage) || '{}'); let data = '{}';
if(savedData.lastupdateDate == null || Date.now() - savedData?.lastupdateDate > this.timeSpan){ try {
this.getSettings() const file = await fs.open(this.keyStorage,'r');
.then(() => { console.log('could not read the certificates from the local file'); return this.rules; }) data = (await file.readFile()).toString('utf-8');
.catch(console.error); 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>{ private async getSettings(): Promise<unknown[]>{
const response = await fetch(`${this.baseUrl}/v1/dgc/settings`); const response:AxiosResponse<unknown[]> = await axios.get(`${this.baseUrl}/v1/dgc/settings`);
const jsonData = await response.json(); const jsonData = response.data;
// this.rules = Rule.fromJSON(jsonData,{valueSets, validationClock:new Date().toISOString(),}) // this.rules = Rule.fromJSON(jsonData,{valueSets, validationClock:new Date().toISOString(),})
this.rules = jsonData; 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; return jsonData;
} }
} }

View 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'};
}
}

View File

@ -1,14 +1,12 @@
import { CertificateDownloader } from './CertificateDownloader'; import { CertificateDownloader } from './CertificateDownloader';
import { RuleDownloader } from './RuleDownloader'; import { RuleDownloader } from './RuleDownloader';
import { VaccineVerifier } from './VaccineVerifier';
import {DCC} from 'dcc-utils'; import {DCC} from 'dcc-utils';
import jsrsasign from 'jsrsasign';
export default class Verifier { export default class Verifier {
static instance: Verifier|undefined = undefined; static instance: Verifier|undefined = undefined;
private certDownloader: CertificateDownloader; private certDownloader: CertificateDownloader;
private ruleDownloader: RuleDownloader; private ruleDownloader: RuleDownloader;
private certificateList: string[] = [];
private constructor(){ private constructor(){
this.certDownloader = new CertificateDownloader(); this.certDownloader = new CertificateDownloader();
this.ruleDownloader = new RuleDownloader(); this.ruleDownloader = new RuleDownloader();
@ -17,21 +15,19 @@ export default class Verifier {
public static async instanceVerifier(): Promise<Verifier>{ public static async instanceVerifier(): Promise<Verifier>{
if (Verifier.instance == undefined){ if (Verifier.instance == undefined){
Verifier.instance = new Verifier(); 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; return Verifier.instance;
} }
async checkCertificate(certificate:string): Promise<unknown>{ async checkCertificate(certificate:string): Promise<unknown>{
console.log(certificate);
const dcc = await DCC.fromRaw(certificate); const dcc = await DCC.fromRaw(certificate);
let result: unknown; let result = await dcc.checkSignatureWithKeysList(await this.certDownloader.getCertificates());
this.certificateList.forEach(async (cert: string) => { const vaccineVerifier = new VaccineVerifier(await this.ruleDownloader.getRules());
const verifier = jsrsasign.KEYUTIL.getKey(cert); result = {signature: result, valid:vaccineVerifier.checkCertifcate(dcc)};
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); console.log(result);
return result; return result;
} }

View File

@ -73,7 +73,7 @@
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */ /* Type Checking */
"strict": true, /* Enable all strict type-checking options. */ // "strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */