<!doctype html>
|
|
<head>
|
|
<link rel="stylesheet" href="style.verify.css">
|
|
<link rel="shortcut icon" href="https://www.pathcheck.org/hubfs/Favicon.png">
|
|
<title>Certificate Verifier</title>
|
|
</head>
|
|
<body>
|
|
<div class="center">
|
|
<div class="full-div">
|
|
<div id="pre-verify-section">
|
|
<h1>Certificate Verifier</h1>
|
|
|
|
<div id="reader" style="margin-top: 10px;margin-bottom: 10px;"></div>
|
|
<h2>Or Paste the Code here:</h2>
|
|
<textarea id="qr-verify" rows="10" style="width:100%;" placeholder="cred:type:version:signature:pubkey:payload"></textarea>
|
|
<br><br>
|
|
<div class="center-in-div">
|
|
<button class="qr-btn" onclick="verifyQRCode()">Verify</button>
|
|
</div>
|
|
</div>
|
|
|
|
<br><br>
|
|
|
|
<div id="post-verify-section" style="display: none;">
|
|
<div class="card center-in-div">
|
|
<h1 id="qr-verify-title" style="text-align: center;"></h1>
|
|
<canvas style="padding:0px 50px" id="qr"></canvas><br/>
|
|
<h1 id="qr-verify-name" style="text-align: center;"></h1>
|
|
<h2 id="qr-verify-verified" style="text-align: center;"></h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="js/qrcode.min.js"></script>
|
|
|
|
<script src="js/elliptic.min.js"></script>
|
|
<script src="js/sha256.js"></script>
|
|
<script src="js/asn1.min.js"></script>
|
|
<script src="js/base32.min.js"></script>
|
|
<script src="js/html5-qrcode.min.js"></script>
|
|
|
|
<script src="js/pcf.js"></script>
|
|
|
|
<script>
|
|
function e(elem) { return document.getElementById(elem); }
|
|
|
|
const monthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
|
|
|
|
function parseDate(str) {
|
|
if(!/^(\d){8}$/.test(str)) return "invalid date";
|
|
var y = str.substr(0,4),
|
|
m = str.substr(4,2),
|
|
d = str.substr(6,2);
|
|
return monthNames[parseInt(m)] + ' ' + d + ', ' + y;
|
|
}
|
|
|
|
function verifyQRCode() {
|
|
let qr = e("qr-verify").value;
|
|
let uri = qr.substring(qr.indexOf("CRED:"));
|
|
if (uri !== "" && uri != null) {
|
|
e("pre-verify-section").style.display = 'none';
|
|
e("post-verify-section").style.display = '';
|
|
|
|
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = PCF.parseURI(uri);
|
|
const result = PCF.downloadKeyVerify(pubKeyLink, payload, signatureBase32NoPad);
|
|
const fields = PCF.getPayloadFields(payload);
|
|
|
|
let params = {margin:0, width:700, errorCorrectionLevel: 'M', color: {dark: '#3654DD' }};
|
|
QRCode.toCanvas(e('qr'), uri, params, function (error) {
|
|
if (result) {
|
|
const imgDim={width:150,height:150}; //logo dimention
|
|
var context = e('qr').getContext('2d');
|
|
var imageObj = new Image();
|
|
imageObj.src = './img/ok-256.png';
|
|
imageObj.onload = function() {
|
|
context.drawImage(imageObj,
|
|
e('qr').width / 2 - imgDim.width / 2 +1,
|
|
e('qr').height / 2 - imgDim.height / 2,imgDim.width,imgDim.height);
|
|
};
|
|
}
|
|
});
|
|
|
|
if (result == null) {
|
|
e('qr-verify-verified').innerHTML = "Unable to Verify";
|
|
} else if (result) {
|
|
e('qr-verify-verified').innerHTML = 'Signed by ' + pubKeyLink + ' on ' + parseDate(fields['date']);
|
|
} else {
|
|
e('qr-verify-verified').innerHTML = "Credential Invalid";
|
|
}
|
|
|
|
if (type == 'BADGE') {
|
|
e('qr-verify-title').innerHTML = "COVID-19 Vaccine Record"
|
|
e('qr-verify-name').innerHTML = fields['name'] + "<br>";
|
|
e('qr-verify-name').innerHTML += parseDate(fields['dob']) + "<br>";
|
|
}
|
|
if (type == 'STATUS') {
|
|
e('qr-verify-title').innerHTML = "COVID-19 Pass"
|
|
e('qr-verify-name').innerHTML = "Status: " + (fields['status'] === '2' ? "Vaccinated" : "Not Vaccinated") + "<br />";
|
|
e('qr-verify-name').innerHTML += "Initials: " + fields['initials'] + "<br>";
|
|
}
|
|
if (type == 'PASSKEY') {
|
|
e('qr-verify-name').innerHTML = "PassKey for: " + fields['name'] + "<br>";
|
|
e('qr-verify-name').innerHTML += "DoB: " + parseDate(fields['dob']);
|
|
}
|
|
if (type == 'LIBERTY') {
|
|
e('qr-verify-title').innerHTML = "COVID-19 Pass"
|
|
e('qr-verify-name').innerHTML = fields['firstName'] + " " + fields['lastName'] + "<br>";
|
|
e('qr-verify-name').innerHTML += fields['dob'];
|
|
}
|
|
} else {
|
|
e('qr-verify-verified').innerHTML = "Certificate not found. ";
|
|
}
|
|
}
|
|
|
|
function onScanSuccess(qrMessage) {
|
|
if (qrMessage !== "" && qrMessage != null) {
|
|
let uri = qrMessage.substring(qrMessage.indexOf("CRED:"));
|
|
e("qr-verify").value = uri;
|
|
verifyQRCode();
|
|
}
|
|
}
|
|
|
|
function onScanFailure(error) {
|
|
console.warn(`QR error = ${error}`);
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
// Starting Scanner
|
|
let html5QrcodeScanner = new Html5QrcodeScanner("reader", { fps: 10, qrbox: 350 }, /* verbose= */ true);
|
|
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
|
|
|
|
// Loading URI from qr parameter.
|
|
const queryString = window.location.search;
|
|
|
|
const urlParams = new URLSearchParams(queryString);
|
|
const qr = urlParams.get('qr')
|
|
|
|
if (qr !== "" && qr != null) {
|
|
uri = qr.substring(qr.indexOf("CRED:"));
|
|
|
|
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = PCF.parseURI(uri);
|
|
decodedFields = payload.split('/');
|
|
|
|
// Making sure the fields have not been decoded by the browser.
|
|
const encodedFields = decodedFields.map(function(field) {
|
|
return encodeURIComponent(field);
|
|
})
|
|
|
|
const decodedFieldsStr = encodedFields.join('/');
|
|
|
|
e("qr-verify").value = [schema, type, version, signatureBase32NoPad, pubKeyLink, decodedFieldsStr].join(":")
|
|
verifyQRCode();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
|