You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

316 lines
15 KiB

<!doctype html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/style.v2.css">
<link rel="stylesheet" href="css/topnav.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://www.pathcheck.org/hubfs/Favicon.png">
<title>Vaccine Distribution Certificates Generator</title>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-DTDMHW3NV6"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-DTDMHW3NV6');
</script>
</head>
<body>
<div class="topnav">
<div class="topnavContainer">
<a href="index.html">Signers</a>
<a href="verify.html"><span class="xs-hidden">Universal </span>Verifier</a>
<a class="xs-hidden" href="debug.html">QR Debugger</a>
<a class="xs-hidden" href="https://github.com/Path-Check/paper-cred-demo">Source Code</a>
<a href="https://github.com/Path-Check/paper-cred"><span class="xs-hidden"> QR </span>Specs</a>
<a href="http://vaccine-docs.pathcheck.org"><span class="xs-hidden">Vaccine </span>Docs</a>
<a class="xs-hidden" href="http://pathcheck.org">About PathCheck</a>
</div>
</div>
<div class="center">
<h1>Vaccine Journey (CRED)</h1>
<div class="full-div">
<div class="quarter">
<h4>Coupon</h4>
<table>
<tr><td>ID</td><td><input id="qr-coupon-id" type="text" placeholder="2"/></td></tr>
<tr><td>Coupons</td><td><input id="qr-coupon-coupons" type="text" placeholder="5000"/></td></tr>
<tr><td>Phase</td><td><input id="qr-coupon-phase" type="text" placeholder="1A, 2A, ..."/></td></tr>
<tr><td>City</td><td><input id="qr-coupon-city" type="text" placeholder="Somerville, MA, US..."/></td></tr>
<tr><td>Limits</td><td><input id="qr-coupon-indicator" type="text" placeholder="Diabetes, Teacher, Healthworker, Under 65, Over 65,...."/></td></tr>
</table>
</div>
<div class="quarter">
<h4>PassKey</h4>
<table>
<tr><td>Name</td><td><input id="qr-passkey-name" type="text" placeholder="Patient Name"/></td></tr>
<tr><td>Phone</td><td><input id="qr-passkey-phone" type="text" placeholder="617 .."/></td></tr>
<tr><td>Brithdate</td><td><input id="qr-passkey-dob" type="text" placeholder="YYYY-MM-DD"/></td></tr>
<tr><td>Salt</td><td><input id="qr-passkey-salt" type="text" placeholder="2342342"/></td></tr>
</table>
</div>
<div class="quarter">
<h4>Badge</h4>
<table>
<tr><td>Date</td><td><input id="qr-badge-date" type="text" placeholder=""/></td></tr>
<tr><td>Manuf</td><td><input id="qr-badge-manuf" type="text" placeholder="Pfizer, Moderna, etc"/></td></tr>
<tr><td>Product</td><td><input id="qr-badge-product" type="text" placeholder="COVID-19"/></td></tr>
<tr><td>Lot#</td><td><input id="qr-badge-lot" type="text" placeholder="012L20A, ..."/></td></tr>
<tr><td>Route</td><td><input id="qr-badge-route" type="text" placeholder="Intramuscular, Intranasal, Subcut, Oral, ..."/></td></tr>
<tr><td>Site</td><td><input id="qr-badge-site" type="text" placeholder="Right Arm, Left Arm, ..."/></td></tr>
<tr><td>Dosage</td><td><input id="qr-badge-dose" type="text" placeholder="1000ul, 500ul, ..."/></td></tr>
<tr><td>Boosts</td><td><input id="qr-badge-required_doses" type="text" placeholder="[28], [21], []"/></td></tr>
<tr><td>PassKey</td><td><input id="qr-badge-vaccinee" type="text" placeholder="User Hash" readonly/></td></tr>
</table>
</div>
<div class="quarter">
<h4>Status</h4>
<table>
<tr><td>Doses</td><td><input id="qr-status-vaccinated" type="text" placeholder="0,1,2"/></td></tr>
<tr><td>PassKey</td><td><input id="qr-status-vaccinee" type="text" placeholder="User Hash" readonly/></td></tr>
</table>
</div>
<div class="quarter">
<h4>Credentials</h4>
<label for="privkey">Private Key</label><br/>
<textarea id="privkey" rows="10" cols="30">-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIPWKbSezZMY1gCpvN42yaVv76Lo47FvSsVZpQl0a5lWRoAcGBSuBBAAK
oUQDQgAE6DeIun4EgMBLUmbtjQw7DilMJ82YIvOR2jz/IK0R/F7/zXY1z+gqvFXf
DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
-----END EC PRIVATE KEY-----</textarea>
<br><br>
<label for="pubkey">Link to Public Key</label><br/>
<textarea id="qr-link" rows="1" cols="30">www.pathcheck.org/hubfs/pub</textarea>
<br><br>
<label for="privkey">QR Code Format</label><br/>
<pre>cred:<span class='protocol'>type:version</span>:<span class='signature'>Signature</span>.<span class='pub-key'>PubKey</span>?<span class='message'>Payload</span></pre>
</div>
</div>
<br>
<div class="four-quarter">
<button class="qr-btn" onclick="generateQRCodes()">Create Certificates</button>
<br>
<br>
</div>
<div class="full-div">
<div class="quarter">
<canvas id="qr-coupon-code"></canvas><br/>
<pre id="qr-coupon-result"></pre>
<p id="qr-coupon-verified"></p>
</div>
<div class="quarter">
<canvas id="qr-passkey-code"></canvas><br/>
<pre id="qr-passkey-result"></pre>
<p id="qr-passkey-verified"></p>
</div>
<div class="quarter">
<canvas id="qr-badge-code"></canvas><br/>
<pre id="qr-badge-result"></pre>
<p id="qr-badge-verified"></p>
</div>
<div class="quarter">
<canvas id="qr-status-code"></canvas><br/>
<pre id="qr-status-result"></pre>
<p id="qr-status-verified"></p>
</div>
<div class="quarter">
</div>
</div>
</div>
<script src="js/qrious.min.js"></script>
<script src="js/elliptic.min.js"></script>
<script src="js/sha256.js"></script>
<script src="js/asn1.min.js"></script>
<script>
var EC = elliptic.ec;
var ANS1 = asn1;
// PEM Definitions
var ECPublicKey = ANS1.define("PublicKey", function() {
this.seq().obj(
this.key("algorithm").seq().obj(
this.key("id").objid(),
this.key("curve").objid()
),
this.key("pub").bitstr()
);
});
var ECPrivateKey = ANS1.define("ECPrivateKey", function() {
this.seq().obj(
this.key('version').int().def(1),
this.key('privateKey').octstr(),
this.key('parameters').explicit(0).objid().optional(),
this.key('publicKey').explicit(1).bitstr().optional()
);
});
const algos = {'1.2.840.10045.2.1':'Elliptic curve public key cryptography'};
const curves = {'1.3.132.0.10': 'secp256k1'};
function e(elem) { return document.getElementById(elem); }
function payloadFrom(elemArray) {
let fields = elemArray.map(function(elemId) {
return encodeURIComponent(e(elemId).value.toUpperCase());
})
return fields.join('/');
}
function hashFrom(elemArray) {
const RS = String.fromCharCode(30);
let fields = elemArray.map(function(elemId) {
return e(elemId).value.toUpperCase();
})
return fields.join(RS);
}
function verify(pubkey, payload, signature, feedback_elem_id) {
try {
// Download pubkey to verify
let client = new XMLHttpRequest();
// Must be downloadable via HTTPs. Cannot redirect.
client.open('GET', "https://" + pubkey);
client.addEventListener("load",
function() {
// Decoding the public key to get algo+key
let pubk = ECPublicKey.decode(this.response, 'pem', {label: 'PUBLIC KEY'});
// Get Encryption Algorithm
let algoCode = pubk.algorithm.id.join('.');
// Get Curve Algorithm
let curveCode = pubk.algorithm.curve.join('.');
// Prepare EC with assigned curve
let ec = new EC(curves[curveCode]);
let key = ec.keyFromPublic(pubk.pub.data, 'der');
// Verifies Signature.
let verified = key.verify(payload, signature);
// Updates screen.
e(feedback_elem_id).innerHTML = "Verified: " + verified;
}
);
client.send();
} catch(err) {
console.log(err);
}
}
function signAndDisplayQR(elemPref, protocol, prikey, pubkey, payload) {
// Load Primary Key
let ec_pk = ECPrivateKey.decode(prikey, 'pem', {label: 'EC PRIVATE KEY'});
// Get Curve Algorithm.
let curveCode = ec_pk.parameters.join('.');
// Prepare EC with assigned curve
let ec = new EC(curves[curveCode]);
let key = ec.keyFromPrivate(ec_pk.privateKey, 'der');
// Signs
let signature = key.sign(payload).toDER('hex');
// Builds URI
let uri = protocol+":"+signature+"."+pubkey+"?"+payload;
// Builds QR Element
let qr = new QRious({ element: e(elemPref+'-code') });
qr.set({
foreground: '#3654DD',
size: 290,
level: 'M',
value: uri
});
// Updates screen elements.
e(elemPref+"-result").innerHTML= "<span class='protocol'>"+protocol+"</span>:" +
"<span class='signature'>" + signature + "</span>" + "." +
"<span class='pub-key'>" + pubkey + "</span>" + "?" +
"<span class='message'>" + payload + "</span>";
e(elemPref+"-verified").innerHTML = "Verified: false";
// Verifies URI is valid
verify(pubkey, payload, signature, elemPref + "-verified");
}
function generateQRCodes() {
gtag('event', 'generateQR');
// Get Keys
let pubkey = e("qr-link").value.trim().replace("http://","");
let prikey = e('privkey').value;
// Build PassKeyHash
let hashPassKey = CryptoJS.SHA256(hashFrom(["qr-passkey-name", "qr-passkey-dob", "qr-passkey-salt"]));
// Update fields on Screen
e("qr-status-vaccinee").value = hashPassKey;
e("qr-badge-vaccinee").value = hashPassKey;
// Coupon QR
let couponArray = ["qr-coupon-id", "qr-coupon-coupons", "qr-coupon-city", "qr-coupon-phase", "qr-coupon-indicator"];
signAndDisplayQR("qr-coupon", "cred:coupon:1", prikey, pubkey, payloadFrom(couponArray));
// PassKey QR
let passKeyArray = ["qr-passkey-name", "qr-passkey-dob", "qr-passkey-salt", "qr-passkey-phone"];
signAndDisplayQR("qr-passkey", "cred:passkey:1", prikey, pubkey, payloadFrom(passKeyArray));
// Badge QR
let badgeArray = ["qr-badge-date", "qr-badge-manuf", "qr-badge-product", "qr-badge-lot",
"qr-badge-required_doses", "qr-badge-vaccinee", "qr-badge-route",
"qr-badge-site", "qr-badge-dose"];
signAndDisplayQR("qr-badge", "cred:badge:1", prikey, pubkey, payloadFrom(badgeArray));
// Status QR
let statusArray = ["qr-status-vaccinated", "qr-status-vaccinee"];
signAndDisplayQR("qr-status", "cred:status:1", prikey, pubkey, payloadFrom(statusArray));
}
</script>
<script>
// Defaults
e("qr-badge-date").value = new Date().toJSON().slice(0, 10).replaceAll("-","");
// Salt
e("qr-passkey-salt").value = Math.random().toString(36).substring(3);
function loadDemo() {
// Salt
e("qr-passkey-salt").value = Math.random().toString(36).substring(3);
e("qr-coupon-id").value = "1";
e("qr-coupon-coupons").value = 5000;
e("qr-coupon-phase").value = "1A";
e("qr-coupon-city").value = "Somerville MA US";
e("qr-coupon-indicator").value = ">65";
e("qr-passkey-name").value = "Jane Doe";
e("qr-passkey-phone").value = "16173332345";
e("qr-passkey-dob").value = "19010101";
e("qr-badge-manuf").value = "Moderna";
e("qr-badge-product").value = "COVID-19";
e("qr-badge-lot").value = "012L20A";
e("qr-badge-route").value = "C28161";
e("qr-badge-site").value = "RA";
e("qr-badge-dose").value = "500";
e("qr-badge-required_doses").value = "28";
e("qr-status-vaccinated").value = "1";
}
loadDemo();
</script>
</body>
</html>