<!doctype html>
|
|
<head>
|
|
<link rel="stylesheet" href="css/style.v2.css">
|
|
<link rel="shortcut icon" href="https://www.pathcheck.org/hubfs/Favicon.png">
|
|
<title>Massachusetts Signed Driver's License Proposal</title>
|
|
</head>
|
|
<body>
|
|
<div class="center">
|
|
<div class="full-div">
|
|
<h3>Massachusetts Signed Driver's License Proposal (<a href="https://github.com/Path-Check/paper-cred/">Spec</a>, <a href="https://vaccine-docs.pathcheck.org/">Main Page</a>, <a href="https://github.com/Path-Check/paper-cred-demo/">Source Code</a>)</h3>
|
|
|
|
<div class="third">
|
|
<h4>License</h4>
|
|
<table>
|
|
<tr><td>Issue</td><td><input id="qr-dbd" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
<tr><td>Expiration</td><td><input id="qr-dba" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
|
|
<tr><td>Juris Vehicle Class</td><td><input id="qr-dca" type="text" placeholder="2"/></td></tr>
|
|
<tr><td>Juris Restrictions</td><td><input id="qr-dcb" type="text" placeholder="Glasses, ..."/></td></tr>
|
|
<tr><td>Juris Privileges</td><td><input id="qr-dcd" type="text" placeholder="1A, 2A, ..."/></td></tr>
|
|
|
|
<tr><td>Juris Vehicle Class Desc</td><td><input id="qr-dcp" type="text" placeholder="2"/></td></tr>
|
|
<tr><td>Juris Endorsement Desc</td><td><input id="qr-dcq" type="text" placeholder="Glasses, ..."/></td></tr>
|
|
<tr><td>Juris Restriction Desc</td><td><input id="qr-dcr" type="text" placeholder="1A, 2A, ..."/></td></tr>
|
|
|
|
<tr><td>Fed Commercial Vehicle</td><td><input id="qr-dch" type="text" placeholder=""/></td></tr>
|
|
|
|
<tr><td>Std Vehicle Class</td><td><input id="qr-dcm" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Std Endorsement</td><td><input id="qr-dcn" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Std Restriction</td><td><input id="qr-dco" type="text" placeholder=""/></td></tr>
|
|
|
|
<tr><td>Compliance Type</td><td><input id="qr-dda" type="text" placeholder="F, N, ..."/></td></tr>
|
|
<tr><td>Card Revision Date</td><td><input id="qr-ddb" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
<tr><td>Card Hazardous Exp.</td><td><input id="qr-ddc" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
|
|
<tr><td>Audit Information</td><td><input id="qr-dcj" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Inventory Control</td><td><input id="qr-dck" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Customer ID</td><td><input id="qr-daq" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Document Discriminator</td><td><input id="qr-dcf" type="text" placeholder=""/></td></tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="third">
|
|
<h4>Driver</h4>
|
|
<table>
|
|
<tr><td>First Name</td><td><input id="qr-dac" type="text" placeholder="First Name ..."/></td></tr>
|
|
<tr><td>Middle Name</td><td><input id="qr-dad" type="text" placeholder="Middle Name ..."/></td></tr>
|
|
<tr><td>Last Name</td><td><input id="qr-dcs" type="text" placeholder="Last Name ..."/></td></tr>
|
|
<tr><td>Given Name</td><td><input id="qr-dct" type="text" placeholder="Given Name ..."/></td></tr>
|
|
<tr><td>Suffix</td><td><input id="qr-dcu" type="text" placeholder="Dr. Jr. ..."/></td></tr>
|
|
|
|
<tr><td>DoB</td><td><input id="qr-dbb" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
<tr><td>Gender</td><td><input id="qr-dbc" type="text" placeholder="Gender ..."/></td></tr>
|
|
|
|
<tr><td>Lawful Status</td><td><input id="qr-ddd" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Organ Donor</td><td><input id="qr-ddk" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Veteran</td><td><input id="qr-ddl" type="text" placeholder=""/></td></tr>
|
|
|
|
<tr><td>Place of Birth</td><td><input id="qr-dci" type="text" placeholder=""/></td></tr>
|
|
|
|
<tr><td>Last Name Alias</td><td><input id="qr-dbn" type="text" placeholder="Last Name ..."/></td></tr>
|
|
<tr><td>Given Name Alias</td><td><input id="qr-dbg" type="text" placeholder="Given Name ..."/></td></tr>
|
|
<tr><td>Suffix Alias</td><td><input id="qr-dbs" type="text" placeholder="Dr. Jr. ..."/></td></tr>
|
|
|
|
<tr><td>18Y Anniversary</td><td><input id="qr-ddh" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
<tr><td>19Y Anniversary</td><td><input id="qr-ddi" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
<tr><td>21Y Anniversary</td><td><input id="qr-ddj" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
<tr><td>ZVA</td><td><input id="qr-zva" type="text" placeholder="YYYYMMDD"/></td></tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="third">
|
|
<h4>Characteristics</h4>
|
|
<table>
|
|
<tr><td>Eye Color</td><td><input id="qr-day" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Hair Color</td><td><input id="qr-daz" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Race</td><td><input id="qr-dcl" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Height</td><td><input id="qr-dau" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Weight Range</td><td><input id="qr-dce" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Weight (lbs)</td><td><input id="qr-daw" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Weight (kgs)</td><td><input id="qr-dax" type="text" placeholder=""/></td></tr>
|
|
</table>
|
|
|
|
<h4>Location</h4>
|
|
<table>
|
|
<tr><td>Address 1</td><td><input id="qr-dag" type="text" placeholder="3rd St"/></td></tr>
|
|
<tr><td>Address 2</td><td><input id="qr-dah" type="text" placeholder="Apt xx"/></td></tr>
|
|
<tr><td>City</td><td><input id="qr-dai" type="text" placeholder="Somerville"/></td></tr>
|
|
<tr><td>State</td><td><input id="qr-daj" type="text" placeholder="MA"/></td></tr>
|
|
<tr><td>Zip</td><td><input id="qr-dak" type="text" placeholder="02141"/></td></tr>
|
|
<tr><td>Country</td><td><input id="qr-dcg" type="text" placeholder="USA"/></td></tr>
|
|
</table>
|
|
|
|
<h4>Protocol</h4>
|
|
<table>
|
|
<tr><td>AAMVAVersion</td><td><input id="qr-aam-version" type="text" placeholder=""/></td></tr>
|
|
<tr><td>Juris Version</td><td><input id="qr-version" type="text" placeholder=""/></td></tr>
|
|
<tr><td>IIN</td><td><input id="qr-iin" type="text" placeholder=""/></td></tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="quarter">
|
|
<h4>Credentials</h4>
|
|
<label for="privkey">Private Key <small>(openssl ecparam -name secp256k1 -genkey -out private.key)</small></label><br/>
|
|
<textarea id="privkey" rows="10" style="width: 100%;">-----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">Public Key <small>(openssl ec -in private.key -pubout -out public.key)</small></label><br/>
|
|
<label for="pubkey"><small>1. DNS TXT Record FQDN</small></label><br/>
|
|
<label for="pubkey"><small>2. Direct URL to PEM Download</small></label><br/>
|
|
<label for="pubkey"><small>3. ID.DATABASE from <a href="https://github.com/Path-Check/paper-cred/tree/benefits_limitations/keys/pcf">PCF</a></small></label><br/>
|
|
<label for="qr-link">KeyID: </label><input id="qr-link" type="text" value="1A9.PCF"></input>
|
|
<br><br>
|
|
<span class="checkboxes" ><input id="include-logos" type="checkbox"><label for="include-logos">Draw Symbols</label></span>
|
|
|
|
<h4>Read More: <a href="https://github.com/Path-Check/paper-cred/">Spec</a>, <a href="https://vaccine-docs.pathcheck.org/">Project</a>, <a href="https://github.com/Path-Check/paper-cred-demo/">Source</a></h4>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="full-div">
|
|
<br><br>
|
|
<div style="margin: 0 auto; width:500px; height:50px;">
|
|
<div class="center-in-div">
|
|
<button class="qr-btn" onclick="generateQRCodes()">Create Certificates</button>
|
|
</div>
|
|
</div>
|
|
<br><br>
|
|
</div>
|
|
<div class="full-div">
|
|
<div class="two-quarter">
|
|
<h4>QR Format</h4>
|
|
<canvas id="qr-massid-code"></canvas><br/>
|
|
<pre id="qr-massid-result"></pre>
|
|
<pre id="qr-massid-verified"></pre>
|
|
<pre id="qr-massid-bytes"></pre>
|
|
</div>
|
|
<div class="two-quarter">
|
|
<h4>PDF 417 Format</h4>
|
|
<canvas id="pdf-massid-code"></canvas><br/>
|
|
</div>
|
|
<div class="quarter">
|
|
<label for="verify">Verify a QR Code</label>
|
|
<div id="reader" style="margin-top: 10px;margin-bottom: 10px;"></div>
|
|
<label for="verify">Or Paste the Code here:</label>
|
|
<textarea id="qr-verify" rows="10" cols="33" placeholder="cred:type:version:signature:pubkey:payload"></textarea>
|
|
<br><br>
|
|
<button class="qr-btn" onclick="verifyQRCode()">Verify</button>
|
|
<br><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>
|
|
<pre id="qr-verify-result"></pre>
|
|
<pre id="qr-verify-verified"></pre>
|
|
</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/libbcmath.js" type="text/javascript"></script>
|
|
<script src="js/bcmath.js" type="text/javascript"></script>
|
|
<script src="js/pdf417.js" type="text/javascript"></script>
|
|
|
|
<script src="js/pcf.js"></script>
|
|
|
|
<script>
|
|
function e(elem) { return document.getElementById(elem); }
|
|
|
|
function getValueArray(elemArray) {
|
|
const fields = elemArray.map(function(elemId) {
|
|
return e(elemId).value;
|
|
})
|
|
return fields;
|
|
}
|
|
|
|
function clearQR(elemPrefix) {
|
|
e(elemPrefix+'-code').getContext('2d').clearRect(0, 0, e(elemPrefix+'-code').width, e(elemPrefix+'-code').height);
|
|
e(elemPrefix+'-result').innerHTML = "";
|
|
e(elemPrefix+'-verified').innerHTML = "";
|
|
e(elemPrefix+'-bytes').innerHTML = "";
|
|
}
|
|
|
|
async function clear() {
|
|
clearQR('qr-massid');
|
|
}
|
|
|
|
function signAndDisplayQR(elemPref, _type, _version, priKeyPEM, pubKeyId, payloadValueArray) {
|
|
const uri = PCF.sign(_type, _version, priKeyPEM, pubKeyId, payloadValueArray);
|
|
const [schema, type, version, signature, pubKeyLink, payload] = PCF.parseURI(uri);
|
|
|
|
const params = { margin:0, width:520, errorCorrectionLevel: 'L', color: {dark: '#3654DD' }};
|
|
|
|
// Builds QR Element
|
|
QRCode.toCanvas(e(elemPref+'-code'), uri, params, function (error) {
|
|
if (e("include-logos").checked) {
|
|
//adding a logo at center
|
|
const imgDim={width:50,height:50}; //logo dimention
|
|
var context = e(elemPref+'-code').getContext('2d');
|
|
var imageObj = new Image();
|
|
if (e(elemPref+'-private')) {
|
|
if (e(elemPref+'-private').checked)
|
|
imageObj.src = './img/closelocker.jpg';
|
|
else
|
|
imageObj.src = './img/openlocker.jpg';
|
|
} else if (elemPref === 'qr-passkey') {
|
|
imageObj.src = './img/passkey.png';
|
|
} else {
|
|
imageObj.src = './img/logo.jpg';
|
|
}
|
|
imageObj.onload = function() {
|
|
context.drawImage(imageObj,
|
|
e(elemPref+'-code').width / 2 - imgDim.width / 2 +1,
|
|
e(elemPref+'-code').height / 2 - imgDim.height / 2,imgDim.width,imgDim.height);
|
|
};
|
|
}
|
|
})
|
|
|
|
PDF417.draw(uri, e("pdf-massid-code"), 3, 2, 2);
|
|
|
|
// Updates screen elements.
|
|
e(elemPref+"-result").innerHTML= schema+":<span class='protocol'>"+type+":"+version+"</span>:" +
|
|
"<span class='signature'>" + signature + "</span>" + ":" +
|
|
"<span class='pub-key'>" + pubKeyLink + "</span>" + ":" +
|
|
"<span class='message'>" + payload + "</span><br>";
|
|
e(elemPref+"-verified").innerHTML = "Signature: Not Verified. ";
|
|
|
|
let qr = QRCode.create(uri, params);
|
|
|
|
let qrQ = QRCode.create(uri, { margin:0, width:275, errorCorrectionLevel: 'Q', color: {dark: '#3654DD' }});
|
|
let qrH = QRCode.create(uri, { margin:0, width:275, errorCorrectionLevel: 'H', color: {dark: '#3654DD' }});
|
|
let qrM = QRCode.create(uri, { margin:0, width:275, errorCorrectionLevel: 'M', color: {dark: '#3654DD' }});
|
|
let qrL = QRCode.create(uri, { margin:0, width:275, errorCorrectionLevel: 'L', color: {dark: '#3654DD' }});
|
|
|
|
e(elemPref+"-bytes").innerHTML = "URI in A/N (5.5bit/char): "+ Math.round(uri.length * 5.5/8) + " bytes<br>";
|
|
|
|
e(elemPref+"-bytes").innerHTML += "<br>QR Size Analysis: ";
|
|
e(elemPref+"-bytes").innerHTML += "<br>- ECC L 7% " + describe(qrL);
|
|
e(elemPref+"-bytes").innerHTML += "<br>- ECC M 15% " + describe(qrM);
|
|
e(elemPref+"-bytes").innerHTML += "<br>- ECC Q 25% " + describe(qrQ);
|
|
e(elemPref+"-bytes").innerHTML += "<br>- ECC H 30% " + describe(qrH);
|
|
|
|
e(elemPref+"-bytes").innerHTML += "<br><br>QR built with " + qr.segments.length + " segments";
|
|
for (i=0; i<qr.segments.length; i++) {
|
|
e(elemPref+"-bytes").innerHTML += "<br>- " + i + ": " + qr.segments[i].mode.id + " " + qr.segments[i].data;
|
|
}
|
|
|
|
e(elemPref + "-verified").innerHTML = PCF.debugDownloadVerify(pubKeyLink, payload, signature);
|
|
}
|
|
|
|
function describe(qr) {
|
|
return qr.modules.size + "x" + qr.modules.size + " bits " + Math.round((qr.modules.size*qr.modules.size)/8) + " bytes ";
|
|
}
|
|
|
|
function generateQRCodes() {
|
|
clear();
|
|
|
|
// Where to Download the public key
|
|
const pubKeyLink = e("qr-link").value.trim().replace("http://","");
|
|
|
|
// PEM code of the private key
|
|
const priKeyPEM = e('privkey').value;
|
|
|
|
const massIdArray = [
|
|
"qr-iin",
|
|
"qr-aam-version",
|
|
"qr-version",
|
|
"qr-dca",
|
|
"qr-dcb",
|
|
"qr-dcd",
|
|
"qr-dba",
|
|
"qr-dac",
|
|
"qr-dad",
|
|
"qr-dcs",
|
|
"qr-dct",
|
|
"qr-dbd",
|
|
"qr-dbb",
|
|
"qr-dbc",
|
|
"qr-day",
|
|
"qr-dau",
|
|
"qr-dag",
|
|
"qr-dai",
|
|
"qr-daj",
|
|
"qr-dak",
|
|
"qr-daq",
|
|
"qr-dcf",
|
|
"qr-dcg",
|
|
"qr-dch",
|
|
//"qr-dde", //Truncated Flags
|
|
//"qr-ddf", //Truncated Flags
|
|
//"qr-ddg", //Truncated Flags
|
|
"qr-dah",
|
|
"qr-daz",
|
|
"qr-dci",
|
|
"qr-dcj",
|
|
"qr-dck",
|
|
"qr-dbn",
|
|
"qr-dbg",
|
|
"qr-dbs",
|
|
"qr-dcu",
|
|
"qr-dce",
|
|
"qr-dcl",
|
|
"qr-dcm",
|
|
"qr-dcn",
|
|
"qr-dco",
|
|
"qr-dcp",
|
|
"qr-dcq",
|
|
"qr-dcr",
|
|
"qr-dda",
|
|
"qr-ddb",
|
|
"qr-ddc",
|
|
"qr-ddd",
|
|
"qr-daw",
|
|
"qr-dax",
|
|
"qr-ddh",
|
|
"qr-ddi",
|
|
"qr-ddj",
|
|
"qr-ddk",
|
|
"qr-ddl",
|
|
"qr-zva"
|
|
];
|
|
|
|
signAndDisplayQR("qr-massid", "us.ma.id", "3", priKeyPEM, pubKeyLink, getValueArray(massIdArray));
|
|
}
|
|
|
|
function verifyQRCode() {
|
|
e("qr-verify-result").innerHTML = PCF.debugParseURI(e("qr-verify").value);
|
|
e('qr-verify-verified').innerHTML = PCF.debugVerify(e("qr-verify").value);
|
|
}
|
|
|
|
function onScanSuccess(qrMessage) {
|
|
e("qr-verify").value = qrMessage;
|
|
verifyQRCode();
|
|
}
|
|
|
|
function onScanFailure(error) {
|
|
// handle scan failure, usually better to ignore and keep scanning
|
|
console.warn(`QR error = ${error}`);
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
function loadDemo() {
|
|
e("qr-aam-version").value = "9";
|
|
e("qr-version").value = "0";
|
|
e("qr-iin").value = "636000";
|
|
e("qr-dca").value = "D";
|
|
e("qr-dcb").value = "K";
|
|
e("qr-dcd").value = "PH";
|
|
e("qr-dba").value = "20241210";
|
|
e("qr-dac").value = "JOHN";
|
|
e("qr-dad").value = "";
|
|
e("qr-dcs").value = "SAMPLE";
|
|
e("qr-dct").value = "";
|
|
e("qr-dbd").value = "20160606";
|
|
e("qr-dbb").value = "19860606";
|
|
e("qr-dbc").value = "1";
|
|
e("qr-day").value = "BRO";
|
|
e("qr-dau").value = "173";
|
|
e("qr-dag").value = "2300 WEST BROAD STREET";
|
|
e("qr-dai").value = "RICHMOND";
|
|
e("qr-daj").value = "VA";
|
|
e("qr-dak").value = "232690000";
|
|
e("qr-daq").value = "T64235789";
|
|
e("qr-dcf").value = "2424244747474786102204";
|
|
e("qr-dcg").value = "USA";
|
|
e("qr-dch").value = "";
|
|
e("qr-dah").value = "APT 22";
|
|
e("qr-daz").value = "WHI";
|
|
e("qr-dci").value = "Boston, MA";
|
|
e("qr-dcj").value = "";
|
|
e("qr-dck").value = "123456789";
|
|
e("qr-dbn").value = "";
|
|
e("qr-dbg").value = "";
|
|
e("qr-dbs").value = "";
|
|
e("qr-dcu").value = "JR";
|
|
e("qr-dce").value = "4";
|
|
e("qr-dcl").value = "BK";
|
|
e("qr-dcm").value = "";
|
|
e("qr-dcn").value = "";
|
|
e("qr-dco").value = "";
|
|
e("qr-dcp").value = "";
|
|
e("qr-dcq").value = "";
|
|
e("qr-dcr").value = "";
|
|
e("qr-dda").value = "F";
|
|
e("qr-ddb").value = "20080606";
|
|
e("qr-ddc").value = "20090606";
|
|
e("qr-ddd").value = "1";
|
|
e("qr-daw").value = "";
|
|
e("qr-dax").value = "";
|
|
e("qr-ddh").value = "20040606";
|
|
e("qr-ddi").value = "20050606";
|
|
e("qr-ddj").value = "20070606";
|
|
e("qr-ddk").value = "1";
|
|
e("qr-ddl").value = "0";
|
|
e("qr-zva").value = "01";
|
|
}
|
|
|
|
loadDemo();
|
|
|
|
let html5QrcodeScanner = new Html5QrcodeScanner("reader", { fps: 10, qrbox: 150 }, /* verbose= */ true);
|
|
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
|
|
</script>
|
|
|
|
<script>
|
|
async function preloadKey() {
|
|
PCF.getKeyId(e("qr-link").value);
|
|
}
|
|
window.onload = function() {
|
|
preloadKey();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
|