Turned off old-style ON pass generation

This commit is contained in:
Ryan Slobojan 2021-10-15 09:20:47 -04:00
parent e3ab251aa5
commit 2629da4cd4
1 changed files with 6 additions and 184 deletions

View File

@ -1,12 +1,11 @@
import {PayloadBody, Receipt, HashTable} from "./payload";
import {PayloadBody} from "./payload";
import * as PdfJS from 'pdfjs-dist/legacy/build/pdf'
import {QRCode} from "jsqr";
import { getCertificatesInfoFromPDF } from "@ninja-labs/verify-pdf"; // ES6
import * as Sentry from '@sentry/react';
import * as Decode from './decode';
import {getScannedJWS, verifyJWS, decodeJWS} from "./shc";
import { PDFPageProxy, TextItem } from 'pdfjs-dist/types/src/display/api';
import { PDFPageProxy } from 'pdfjs-dist/types/src/display/api';
// import {PNG} from 'pngjs'
// import {decodeData} from "./decode";
@ -22,19 +21,9 @@ export async function getPayloadBodyFromFile(file: File): Promise<PayloadBody> {
switch (file.type) {
case 'application/pdf':
///////// DELETE THE FOLLOWING CODE ON OCT 22ND //////////////////////
const receiptType = await detectReceiptType(fileBuffer);
console.log(`receiptType = ${receiptType}`);
// receipt type is needed to decide if digital signature checking is needed
if (receiptType == 'ON') {
// Bail out immediately, special case
const receipts = await loadPDF(fileBuffer);
return {receipts, rawData: '', shcReceipt: null};
} else {
///////// END OCT 22ND DELETE BLOCK //////////////////////
imageData = await getImageDataFromPdf(fileBuffer);
}
imageData = await getImageDataFromPdf(fileBuffer);
break;
case 'image/png':
case 'image/jpeg':
case 'image/webp':
@ -42,6 +31,7 @@ export async function getPayloadBodyFromFile(file: File): Promise<PayloadBody> {
console.log(`image ${file.type}`);
imageData = [await getImageDataFromImage(file)];
break;
default:
throw Error('invalidFileType')
}
@ -50,174 +40,6 @@ export async function getPayloadBodyFromFile(file: File): Promise<PayloadBody> {
return processSHC(imageData);
}
async function detectReceiptType(fileBuffer : ArrayBuffer): Promise<string> {
console.log('detectPDFTypeAndProcess');
const typedArray = new Uint8Array(fileBuffer);
let loadingTask = PdfJS.getDocument(typedArray);
const pdfDocument = await loadingTask.promise;
const docMetadata = await pdfDocument.getMetadata();
// Explicitly try to detect an ON PDF based on the headers in the PDF
//console.log(`PDF details: metadata=${JSON.stringify(docMetadata)}`);
// The Ontario proof-of-vaccination receipts have several fixed unchanging pieces of metadata that we use for detection
if (docMetadata.info['IsSignaturesPresent'] &&
(docMetadata.info['Producer'] == 'PDFKit') &&
(docMetadata.info['PDFFormatVersion'] == '1.7') &&
(docMetadata.info['Title'] == 'COVID-19 vaccination receipt / Récépissé de vaccination contre la COVID-19')
) {
return Promise.resolve('ON');
} else {
// If it's not an exact match to an ON proof-of-vaccination PDF, it will never pass validation anyways so treat it as an SHC PDF
return Promise.resolve('SHC');
}
}
async function loadPDF(fileBuffer : ArrayBuffer): Promise<HashTable<Receipt>> {
try {
const certs = getCertificatesInfoFromPDF(fileBuffer);
const result = certs[0];
const refcert = '-----BEGIN CERTIFICATE-----\r\n'+
'MIIHNTCCBh2gAwIBAgIQanhJa+fBXT8GQ8QG/t9p4TANBgkqhkiG9w0BAQsFADCB\r\n'+
'ujELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsT\r\n'+
'H1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAy\r\n'+
'MDE0IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEuMCwG\r\n'+
'A1UEAxMlRW50cnVzdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEwxTTAeFw0y\r\n'+
'MTA1MjAxMzQxNTBaFw0yMjA2MTkxMzQxNDlaMIHTMQswCQYDVQQGEwJDQTEQMA4G\r\n'+
'A1UECBMHT250YXJpbzEQMA4GA1UEBxMHVG9yb250bzETMBEGCysGAQQBgjc8AgED\r\n'+
'EwJDQTEYMBYGCysGAQQBgjc8AgECEwdPbnRhcmlvMRcwFQYDVQQKEw5PbnRhcmlv\r\n'+
'IEhlYWx0aDEaMBgGA1UEDxMRR292ZXJubWVudCBFbnRpdHkxEzARBgNVBAUTCjE4\r\n'+
'LTA0LTIwMTkxJzAlBgNVBAMTHmNvdmlkMTlzaWduZXIub250YXJpb2hlYWx0aC5j\r\n'+
'YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2bD+Ng1RNYCNVVtEQ3\r\n'+
'zg8JKFvRWFFPIF/UTXGg3iArK1tKr1xtjx6OdFtwosHyo+3ksPRicc4KeuV6/QMF\r\n'+
'qiVJ5IOy9TSVImJsmONgFyEiak0dGYG5SeHiWwyaUvkniWd7U3wWEl4nOZuLAYu4\r\n'+
'8ZLot8p8Q/UaNvAoNsRDv6YDGjL2yGHaXxi3Bb6XTQTLcevuEQeM6g1LtKyisZfB\r\n'+
'Q8TKThBq99EojwHfXIhddxbPKLeXvWJgK1TcL17UFIwx6ig74s0LyYqEPm8Oa8qR\r\n'+
'+IesFUT9Liv7xhV+tU52wmNfDi4znmLvs5Cmh/vmcHKyhEbxhYqciWJocACth5ij\r\n'+
'E3kCAwEAAaOCAxowggMWMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFoW3zt+jaHS\r\n'+
'pm1EV5hU4XD+mwO5MB8GA1UdIwQYMBaAFMP30LUqMK2vDZEhcDlU3byJcMc6MGgG\r\n'+
'CCsGAQUFBwEBBFwwWjAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZW50cnVzdC5u\r\n'+
'ZXQwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9haWEuZW50cnVzdC5uZXQvbDFtLWNoYWlu\r\n'+
'MjU2LmNlcjAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmVudHJ1c3QubmV0\r\n'+
'L2xldmVsMW0uY3JsMCkGA1UdEQQiMCCCHmNvdmlkMTlzaWduZXIub250YXJpb2hl\r\n'+
'YWx0aC5jYTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\r\n'+
'AQUFBwMCMEsGA1UdIAREMEIwNwYKYIZIAYb6bAoBAjApMCcGCCsGAQUFBwIBFhto\r\n'+
'dHRwczovL3d3dy5lbnRydXN0Lm5ldC9ycGEwBwYFZ4EMAQEwggF+BgorBgEEAdZ5\r\n'+
'AgQCBIIBbgSCAWoBaAB3AFYUBpov18Ls0/XhvUSyPsdGdrm8mRFcwO+UmFXWidDd\r\n'+
'AAABeYoCz+MAAAQDAEgwRgIhAKGKAoZMzwkh/3sZXq6vtEYhoYHfZzsjh9jqZvfS\r\n'+
'xQVZAiEAmJu/ftbkNFBr8751Z9wA2dpI0Qt+LoeL1TJQ833Kdg4AdQDfpV6raIJP\r\n'+
'H2yt7rhfTj5a6s2iEqRqXo47EsAgRFwqcwAAAXmKAs/cAAAEAwBGMEQCICsD/Vj+\r\n'+
'ypZeHhesMyv/TkS5ftQjqyIaAFTL/02Gtem4AiBcWdPQspH3vfzZr4LO9z4u5jTg\r\n'+
'Psfm5PZr66tI7yASrAB2AEalVet1+pEgMLWiiWn0830RLEF0vv1JuIWr8vxw/m1H\r\n'+
'AAABeYoC0WkAAAQDAEcwRQIgTL5F11+7KhQ60jnODm9AkyvXRLY32Mj6tgudRAXO\r\n'+
'y7UCIQDd/dU+Ax1y15yiAA5xM+bWJ7T+Ztd99SD1lw/o8fEmOjANBgkqhkiG9w0B\r\n'+
'AQsFAAOCAQEAlpV3RoNvnhDgd2iFSF39wytf1R6/0u5FdL7eIkYNfnkqXu9Ux9cO\r\n'+
'/OeaGAFMSzaDPA8Xt9A0HqkEbh1pr7UmZVqBwDr4a7gczvt7+HFJRn//Q2fwhmaw\r\n'+
'vXTLLxcAPQF00G6ySsc9MUbsArh6AVhMf9tSXgNaTDj3X3UyYDfR+G8H9eVG/LPp\r\n'+
'34QV/8uvPUFXGj6MjdQysx6YG+K3mae0GEVpODEl4MiceEFZ7v4CPA6pFNadijRF\r\n'+
'6tdXky2psuo7VXfnE2WIlahKr56x+8R6To5pcWglKTywTqvCbnKRRVZhXXYo3Awd\r\n'+
'8h9+TbL3ACHDqA4fi5sAbZ7nMXp8RK4o5A==\r\n'+
'-----END CERTIFICATE-----';
const pdfCert = result.pemCertificate.trim();
//const pdfOrg = result.issuedBy.organizationName;
const issuedpemCertificate = (pdfCert == refcert.trim());
//console.log(`pdf is signed by this cert ${result.pemCertificate.trim()}`);
//console.log(issuedpemCertificate);
//console.log(`PDF is signed by ${result.issuedBy.organizationName}, issued to ${result.issuedTo.commonName}`);
if (issuedpemCertificate) {
//console.log('getting receipt details inside PDF');
const receipt = await getPdfDetails(fileBuffer);
// console.log(JSON.stringify(receipt, null, 2));
return Promise.resolve(receipt);
} else {
/* We don't need to track these anymore - this is all going away in a week anyways
// According to the Sentry docs, this can be up to 8KB in size
// https://develop.sentry.dev/sdk/data-handling/#variable-size
Sentry.setContext("certificate", {
pdfCert: pdfCert,
pdfOrg: pdfOrg,
});
Sentry.captureMessage('Certificate validation failed');
*/
console.error('invalid certificate');
return Promise.reject(`invalid certificate + ${JSON.stringify(result)}`);
}
} catch (e) {
if (e.message.includes('Failed to locate ByteRange') ||
e.message.includes(' ASN.1') ||
e.message.includes('Failed byte range verification') ||
e.message.includes('parse DER') ||
e.message.includes('8, 16, 24, or 32 bits')) {
e.message = 'Sorry. Selected PDF file is not digitally signed. Please download official copy from Step 1 and retry. Thanks.'
} else {
if (!e.message.includes('cancelled')) {
console.error(e);
Sentry.captureException(e);
}
}
return Promise.reject(e);
}
}
async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<HashTable<Receipt>> {
try {
const typedArray = new Uint8Array(fileBuffer);
let loadingTask = PdfJS.getDocument(typedArray);
const pdfDocument = await loadingTask.promise;
// Load all dose numbers
const receiptObj = {};
for (let pages = 1; pages <= pdfDocument.numPages; pages++){
const pdfPage = await pdfDocument.getPage(pages);
const content = await pdfPage.getTextContent();
const numItems = content.items.length;
let name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization;
for (let i = 0; i < numItems; i++) {
let item = content.items[i] as TextItem;
const value = item.str;
if (value.includes('Name / Nom'))
name = (content.items[i+1] as TextItem).str;
if (value.includes('Date:')) {
vaccinationDate = (content.items[i+1] as TextItem).str;
vaccinationDate = vaccinationDate.split(',')[0];
}
if (value.includes('Product name')) {
vaccineName = (content.items[i+1] as TextItem).str;
}
if (value.includes('Date of birth'))
dateOfBirth = (content.items[i+1] as TextItem).str;
if (value.includes('Authorized organization'))
organization = (content.items[i+1] as TextItem).str;
if (value.includes('You have received'))
numDoses = Number(value.split(' ')[3]);
}
receiptObj[numDoses] = new Receipt(name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization);
//console.log(receiptObj[numDoses]);
}
return Promise.resolve(receiptObj);
} catch (e) {
if (e && e.message && !e.message.includes('cancelled')) {
Sentry.captureException(e);
}
return Promise.reject(e);
}
}
async function getImageDataFromPdfPage(pdfPage: PDFPageProxy): Promise<ImageData> {
const pdfScale = 2;
@ -350,7 +172,7 @@ async function processSHC(allImageData : ImageData[]) : Promise<PayloadBody> {
// If we got here, no SHC was detected and successfully decoded.
// The vast majority of our processed things right now are ON proof-of-vaccination PDFs, not SHC docs, so assume anything
// that blew up here was a malformed ON proof-of-vaccination and create an appropriate error message for that
return Promise.reject(new Error('No valid ON proof-of-vaccination digital signature found! Please make sure you download the PDF directly from covid19.ontariohealth.ca, Save as Files on your iPhone, and do NOT save/print it as a PDF!'));
return Promise.reject(new Error('No SHC QR code found! Please try taking another picture of the SHC you wish to import'));
} catch (e) {
Sentry.captureException(e);