Browse Source

Refactoring PCF CRED functions into it's own JS File.

pull/1/head
Vitor Pamplona 5 years ago
parent
commit
a00cec4a6c
4 changed files with 336 additions and 728 deletions
  1. +34
    -263
      index.v5.html
  2. +262
    -0
      js/pcf.js
  3. +21
    -235
      us.ma.id.html
  4. +19
    -230
      verify.html

+ 34
- 263
index.v5.html View File

@ -151,87 +151,21 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
<script src="js/bcmath.js" type="text/javascript"></script>
<script src="js/pdf417.js" type="text/javascript"></script>
<script>
var localPubKeyDB = {};
const EC = elliptic.ec;
const ANS1 = asn1;
// PEM Definitions
const 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()
);
});
const 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'};
<script src="js/pcf.js"></script>
<script>
function e(elem) { return document.getElementById(elem); }
function buildPayload(elemArray) {
function getValueArray(elemArray) {
const fields = elemArray.map(function(elemId) {
return encodeURIComponent(e(elemId).value.toUpperCase());
return e(elemId).value;
})
return fields.join('/');
}
function buildHashPayload(elemArray) {
const RS = String.fromCharCode(30);
let fields = elemArray.map(function(elemId) {
return e(elemId).value.toUpperCase();
})
return fields.join(RS);
}
function pad(base32Str) {
switch (base32Str.length % 8) {
case 2: return base32Str + "======";
case 4: return base32Str + "====";
case 5: return base32Str + "===";
case 7: return base32Str + "=";
}
return base32Str;
}
function rmPad(base32Str) {
return base32Str.replaceAll("=", "");
return fields;
}
function verifySignature(pubkey, payload, signatureBase32NoPad, feedback_elem_id) {
try {
// Decoding the public key to get curve algorithm + key itself
const pubk = ECPublicKey.decode(pubkey, 'pem', {label: 'PUBLIC KEY'});
// Get Encryption Algorithm: EC
const algoCode = pubk.algorithm.id.join('.');
// Get Curve Algorithm: secp256k1
const curveCode = pubk.algorithm.curve.join('.');
// Prepare EC with assigned curve
const ec = new EC(curves[curveCode]);
// Load public key from PEM as DER
const key = ec.keyFromPublic(pubk.pub.data, 'der');
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Gets the Base32 enconded, add padding and convert to DER.
const signatureDER = base32.decode.asBytes(pad(signatureBase32NoPad));
// Verifies Signature.
const verified = key.verify(payloadSHA256, signatureDER);
const verified = PCF.verify(pubkey, payload, signatureBase32NoPad, feedback_elem_id)
// Updates screen.
e(feedback_elem_id).innerHTML = "Signature: " + (verified ? "Independently Verified" : "Not Valid");
@ -256,87 +190,18 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
function downloadAndVerify(pubkeyURL, payload, signatureBase32NoPad, feedback_elem_id) {
// Download pubkey to verify
let client = new XMLHttpRequest();
if (localPubKeyDB[pubkeyURL]) {
verifySignature(localPubKeyDB[pubkeyURL], payload, signatureBase32NoPad, feedback_elem_id);
return;
}
try {
// Try to download from the DNS TXT record.
client.open('GET', "https://dns.google/resolve?name=" + pubkeyURL + '&type=TXT', false);
client.send();
const jsonResponse = JSON.parse(client.response);
if (jsonResponse.Answer) {
const pubKeyTxtLookup = JSON.parse(client.response).Answer[0].data
const noQuotes = pubKeyTxtLookup.substring(1, pubKeyTxtLookup.length - 1).replaceAll("\\n","\n");
if (noQuotes) {
localPubKeyDB[pubkeyURL] = noQuotes;
verifySignature(noQuotes, payload, signatureBase32NoPad, feedback_elem_id);
return;
}
}
} catch(err) {
e(feedback_elem_id).innerHTML += "Verification Failed: " + err;
console.error(err);
}
try {
// Try to download as a file.
client.open('GET', "https://" + pubkeyURL, false);
client.send();
if (client.response.includes("-----BEGIN PUBLIC KEY-----")) {
localPubKeyDB[pubkeyURL] = client.response;
verifySignature(client.response, payload, signatureBase32NoPad, feedback_elem_id);
return;
}
} catch(err) {
console.error(err);
}
let publicKeyPEM = PCF.getKeyId(pubkeyURL);
try {
let publicKey = getGitHubDatabase(pubkeyURL.split('.')[0], pubkeyURL.split('.')[1]);
if (publicKey != undefined && publicKey.includes("-----BEGIN PUBLIC KEY-----")) {
localPubKeyDB[pubkeyURL] = publicKey;
verifySignature(publicKey, payload, signatureBase32NoPad, feedback_elem_id);
return;
} else {
console.error("GitHub Not Found: "+ publicKey);
}
} catch(err) {
console.error(err);
if (publicKeyPEM !== null) {
verifySignature(publicKeyPEM, payload, signatureBase32NoPad, feedback_elem_id);
} else {
e(feedback_elem_id).innerHTML += "Verification Failed: Public Key not found.";
}
}
function signAndDisplayQR(elemPref, protocol, priKeyPEM, pubKeyLink, payload) {
// Load Primary Key
const ec_pk = ECPrivateKey.decode(priKeyPEM, 'pem', {label: 'EC PRIVATE KEY'});
// Get Curve Algorithm.
const curveCode = ec_pk.parameters.join('.');
// Prepare EC with assigned curve: secp256k1
const ec = new EC(curves[curveCode]);
// Load Private Key from PEM file converted to DER.
const key = ec.keyFromPrivate(ec_pk.privateKey, 'der');
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Signs, gets a DER
const signatureDER = key.sign(payloadSHA256).toDER();
// Converts DER to Base32 and remove padding.
const signature = rmPad(base32.encode(signatureDER));
// Builds URI
const uri = protocol.toUpperCase()+":"+signature+":"+pubKeyLink.toUpperCase()+":"+payload;
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:275, errorCorrectionLevel: 'L', color: {dark: '#3654DD' }};
@ -371,9 +236,9 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
e(elemPref+"-pdf-label").style.display = '';
// Updates screen elements.
e(elemPref+"-result").innerHTML= "<span class='protocol'>"+protocol.toUpperCase()+"</span>:" +
e(elemPref+"-result").innerHTML= schema+":<span class='protocol'>"+type+":"+version+"</span>:" +
"<span class='signature'>" + signature + "</span>" + ":" +
"<span class='pub-key'>" + pubKeyLink.toUpperCase() + "</span>" + ":" +
"<span class='pub-key'>" + pubKeyLink + "</span>" + ":" +
"<span class='message'>" + payload + "</span><br>";
// "Signature DER: <span class='signature'>" + signatureDER + "</span><br><br>"+
// "Payload SHA256 Hash: <span class='message'>" + payloadSHA256 + "</span><br><br>";
@ -402,7 +267,7 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
// Verifies URI is valid
downloadAndVerify(pubKeyLink.toUpperCase(), payload, signature, elemPref + "-verified");
downloadAndVerify(pubKeyLink, payload, signature, elemPref + "-verified");
}
function describe(qr) {
@ -419,9 +284,7 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
const priKeyPEM = e('privkey').value;
// Build PassKeyHash
const hashedPassKey = buildHashPayload(["qr-passkey-name", "qr-passkey-dob", "qr-passkey-salt", "qr-passkey-phone"]);
const digest = CryptoJS.SHA256(hashedPassKey).toString();
const hashPassKeyBase32 = rmPad(base32.encode(digest));
const hashPassKeyBase32 = PCF.getPayloadHash(getValueArray(["qr-passkey-name", "qr-passkey-dob", "qr-passkey-salt", "qr-passkey-phone"]));
if (e("qr-badge-private").checked) {
// Update fields on Screen
@ -443,100 +306,24 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
// Coupon QR
const couponArray = ["qr-coupon-id", "qr-coupon-coupons", "qr-coupon-city", "qr-coupon-phase", "qr-coupon-indicator"];
signAndDisplayQR("qr-coupon", "cred:coupon:1", priKeyPEM, pubKeyLink, buildPayload(couponArray));
const couponArray = getValueArray(["qr-coupon-id", "qr-coupon-coupons", "qr-coupon-city", "qr-coupon-phase", "qr-coupon-indicator"]);
signAndDisplayQR("qr-coupon", "coupon","1", priKeyPEM, pubKeyLink, couponArray);
// PassKey QR
const passKeyArray = ["qr-passkey-name", "qr-passkey-dob", "qr-passkey-salt", "qr-passkey-phone"];
signAndDisplayQR("qr-passkey", "cred:passkey:1", priKeyPEM, pubKeyLink, buildPayload(passKeyArray));
const passKeyArray = getValueArray(["qr-passkey-name", "qr-passkey-dob", "qr-passkey-salt", "qr-passkey-phone"]);
signAndDisplayQR("qr-passkey", "passkey","1", priKeyPEM, pubKeyLink, passKeyArray);
// Badge QR
const badgeArray = ["qr-badge-date", "qr-badge-manuf", "qr-badge-product", "qr-badge-lot",
const badgeArray = getValueArray(["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", "qr-badge-name", "qr-badge-dob"];
signAndDisplayQR("qr-badge", "cred:badge:2", priKeyPEM, pubKeyLink, buildPayload(badgeArray));
"qr-badge-site", "qr-badge-dose", "qr-badge-name", "qr-badge-dob"]);
signAndDisplayQR("qr-badge", "badge","2", priKeyPEM, pubKeyLink, badgeArray);
// Status QR
const statusArray = ["qr-status-vaccinated", "qr-status-vaccinee", "qr-status-vaccinee-red"];
signAndDisplayQR("qr-status", "cred:status:2", priKeyPEM, pubKeyLink, buildPayload(statusArray));
}
function parseQR(qr) {
return [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = qr.split(':');
}
function getPayloadTypes() {
let client = new XMLHttpRequest();
try {
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/main", false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const filesRoot = JSON.parse(client.response).tree
const payloadsDir = filesRoot.find(element => element.path === 'payloads');
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/"+payloadsDir.sha, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const payloads = JSON.parse(client.response).tree.map(x => x.path.replaceAll(".md","").replaceAll(".",":"));
return payloads;
} catch(err) {
console.error(err);
}
}
function getTXT(url) {
let client = new XMLHttpRequest();
client.open('GET', url, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
return client.response;
}
function getJSON(url) {
return JSON.parse(getTXT(url));
}
function getGitHubDatabase(id, database) {
const githubTree = "https://api.github.com/repos/Path-Check/paper-cred/git/trees";
const githubBlob = "https://api.github.com/repos/Path-Check/paper-cred/git/blobs";
try {
const rootDir = getJSON(githubTree + "/" + "benefits_limitations").tree
const databasesDir = rootDir.find(element => element.path === 'keys');
if (databasesDir === undefined) {
console.debug("Keys Directory not Found on GitHub");
return;
}
const databases = getJSON(githubTree+"/"+databasesDir.sha).tree
const databaseDir = databases.find(element => element.path === database);
if (databaseDir === undefined) {
console.debug("Database not found on GitHub " + database);
return;
}
const idsFiles = getJSON(githubTree+"/"+databaseDir.sha).tree
const idFile = idsFiles.find(element => element.path === id+'.pem');
if (idFile === undefined) {
console.debug("Id not found on GitHub " + id);
return;
}
const publicKeyPem = atob(getJSON(githubBlob+"/"+idFile.sha).content)
return publicKeyPem;
} catch(err) {
console.error(err);
}
const statusArray = getValueArray(["qr-status-vaccinated", "qr-status-vaccinee", "qr-status-vaccinee-red"]);
signAndDisplayQR("qr-status", "status","2", priKeyPEM, pubKeyLink, statusArray);
}
function verifyQRCode() {
e("qr-verify-result").innerHTML = "";
e('qr-verify-verified').innerHTML = "";
@ -549,17 +336,18 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
try {
parseQR(qr);
PCF.parseURI(qr);
e('qr-verify-verified').innerHTML += "QR was parsed sucessfully!<br><br>";
} catch (err) {
e('qr-verify-verified').innerHTML += "Could not parse string into the URI format.<br>";
console.error(err);
return;
}
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = parseQR(qr);
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = PCF.parseURI(qr);
// Updates screen elements.
e("qr-verify-result").innerHTML = "<span class='protocol'>"+schema+":"+type+":"+version+"</span>:" +
e("qr-verify-result").innerHTML = schema+":<span class='protocol'>"+type+":"+version+"</span>:" +
"<span class='signature'>" + signatureBase32NoPad + "</span>" + ":" +
"<span class='pub-key'>" + pubKeyLink + "</span>" + ":" +
"<span class='message'>" + payload + "</span><br><br>";
@ -568,13 +356,11 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
e('qr-verify-verified').innerHTML += "QR is not a credential: Code must start with CRED instead of "+schema +".<br>";
}
const availablePayloads = getPayloadTypes();
if (!availablePayloads.includes(type.toLowerCase()+":"+version)) {
if (!PCF.getPayloadTypes().includes(type.toLowerCase()+":"+version)) {
e('qr-verify-verified').innerHTML += "Type or version <b>" + type + ":" + version + "</b> was not recognized. Make sure this payload type and version are available on <a href='https://github.com/Path-Check/paper-cred/tree/main/payloads'>GitHub</a> <br>";
}
if (e('qr-verify-verified').innerHTML === "QR was parsed sucessfully!<br><br>") {
if (e('qr-verify-verified').innerHTML.includes("QR was parsed sucessfully!")) {
downloadAndVerify(pubKeyLink, payload, signatureBase32NoPad, 'qr-verify-verified');
} else {
e('qr-verify-verified').innerHTML += "<br>Signature: not verified";
@ -590,7 +376,7 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
return initials + dob.substring(2,4);
};
}
function onScanSuccess(qrMessage) {
e("qr-verify").value = qrMessage;
@ -667,21 +453,6 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
loadDemo();
//generateQRCodes();
/*
e('privkey').onchange = function(){
generateQRCodes();
}
var inputList = document.getElementsByTagName("input");
for(var i=0;i<inputList.length;i++){
inputList[i].onchange = function(){
generateQRCodes();
}
inputList[i].onblur = function(){
generateQRCodes();
}
}*/
let html5QrcodeScanner = new Html5QrcodeScanner("reader", { fps: 10, qrbox: 150 }, /* verbose= */ true);
html5QrcodeScanner.render(onScanSuccess, onScanFailure);


+ 262
- 0
js/pcf.js View File

@ -0,0 +1,262 @@
var PCF = {
localPubKeyDB: {},
localPayloadsDB: [],
// PEM Definitions
ECPublicKey: asn1.define("PublicKey", function() {
this.seq().obj(
this.key("algorithm").seq().obj(
this.key("id").objid(),
this.key("curve").objid()
),
this.key("pub").bitstr()
);
}),
ECPrivateKey: asn1.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()
);
}),
algos: {'1.2.840.10045.2.1':'Elliptic curve public key cryptography'},
curves: {'1.3.132.0.10': 'secp256k1'},
buildPayload: function(valueArray) {
const fields = valueArray.map(function(elem) {
return encodeURIComponent(elem.toUpperCase());
})
return fields.join('/');
},
parsePayload(payload) {
const encodedFields = payload.split("/");
const decodedFields = encodedFields.map(function(field) {
return decodeURIComponent(field);
})
return decodedFields;
},
buildHashPayload: function (elemArray) {
const RS = String.fromCharCode(30);
let fields = elemArray.map(function(elem) {
return elem.toUpperCase();
})
return fields.join(RS);
},
getPayloadHash: function(fields) {
const hashedPassKey = this.buildHashPayload(fields);
const digest = CryptoJS.SHA256(hashedPassKey).toString();
const hashPassKeyBase32 = this.rmPad(base32.encode(digest));
return hashPassKeyBase32;
},
pad: function (base32Str) {
switch (base32Str.length % 8) {
case 2: return base32Str + "======";
case 4: return base32Str + "====";
case 5: return base32Str + "===";
case 7: return base32Str + "=";
}
return base32Str;
},
rmPad: function(base32Str) {
return base32Str.replaceAll("=", "");
},
verify: function(pubkey, payload, signatureBase32NoPad, feedback_elem_id) {
// Decoding the public key to get curve algorithm + key itself
const pubk = this.ECPublicKey.decode(pubkey, 'pem', {label: 'PUBLIC KEY'});
// Get Encryption Algorithm: EC
const algoCode = pubk.algorithm.id.join('.');
// Get Curve Algorithm: secp256k1
const curveCode = pubk.algorithm.curve.join('.');
// Prepare EC with assigned curve
const ec = new elliptic.ec(this.curves[curveCode]);
// Load public key from PEM as DER
const key = ec.keyFromPublic(pubk.pub.data, 'der');
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Gets the Base32 enconded, add padding and convert to DER.
const signatureDER = base32.decode.asBytes(this.pad(signatureBase32NoPad));
// Verifies Signature.
return key.verify(payloadSHA256, signatureDER);
},
sign: function(type, version, priKeyPEM, pubKeyLink, payloadValueArray) {
// Load Primary Key
const ec_pk = this.ECPrivateKey.decode(priKeyPEM, 'pem', {label: 'EC PRIVATE KEY'});
// Get Curve Algorithm.
const curveCode = ec_pk.parameters.join('.');
// Prepare EC with assigned curve: secp256k1
const ec = new elliptic.ec(this.curves[curveCode]);
// Load Private Key from PEM file converted to DER.
const key = ec.keyFromPrivate(ec_pk.privateKey, 'der');
// Assemble Payload
const payload = this.buildPayload(payloadValueArray);
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Signs, gets a DER
const signatureDER = key.sign(payloadSHA256).toDER();
// Converts DER to Base32 and remove padding.
const signature = this.rmPad(base32.encode(signatureDER));
return this.formatURI(type, version, signature, pubKeyLink, payload);
},
parseURI: function(uri) {
return [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = uri.split(':');
},
formatURI: function(type, version, signature, pubKeyLink, payload) {
return ["CRED", type.toUpperCase(), version.toUpperCase(), signature, pubKeyLink.toUpperCase(), payload].join(":");
},
getPayloadTypes: function() {
if (this.localPayloadsDB.length > 0) return this.localPayloadsDB;
let client = new XMLHttpRequest();
try {
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/main", false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const filesRoot = JSON.parse(client.response).tree
const payloadsDir = filesRoot.find(element => element.path === 'payloads');
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/"+payloadsDir.sha, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
this.localPayloadsDB = JSON.parse(client.response).tree.map(x => x.path.replaceAll(".md","").replaceAll(".",":"));
return this.localPayloadsDB;
} catch(err) {
console.error(err);
}
},
getTXT: function(url) {
let client = new XMLHttpRequest();
client.open('GET', url, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
return client.response;
},
getJSON: function(url) {
return JSON.parse(this.getTXT(url));
},
getGitHubDatabase: function(id, database) {
const githubTree = "https://api.github.com/repos/Path-Check/paper-cred/git/trees";
const githubBlob = "https://api.github.com/repos/Path-Check/paper-cred/git/blobs";
try {
const rootDir = this.getJSON(githubTree + "/" + "benefits_limitations").tree
const databasesDir = rootDir.find(element => element.path === 'keys');
if (databasesDir === undefined) {
console.debug("Keys Directory not Found on GitHub");
return;
}
const databases = this.getJSON(githubTree+"/"+databasesDir.sha).tree
const databaseDir = databases.find(element => element.path === database);
if (databaseDir === undefined) {
console.debug("Database not found on GitHub " + database);
return;
}
const idsFiles = this.getJSON(githubTree+"/"+databaseDir.sha).tree
const idFile = idsFiles.find(element => element.path === id+'.pem');
if (idFile === undefined) {
console.debug("Id not found on GitHub " + id);
return;
}
const publicKeyPem = atob(this.getJSON(githubBlob+"/"+idFile.sha).content)
return publicKeyPem;
} catch(err) {
console.error(err);
}
},
getKeyId: function (pubkeyURL) {
// Download pubkey to verify
let client = new XMLHttpRequest();
if (this.localPubKeyDB[pubkeyURL]) {
return this.localPubKeyDB[pubkeyURL];
}
try {
// Try to download from the DNS TXT record.
client.open('GET', "https://dns.google/resolve?name=" + pubkeyURL + '&type=TXT', false);
client.send();
const jsonResponse = JSON.parse(client.response);
if (jsonResponse.Answer) {
const pubKeyTxtLookup = JSON.parse(client.response).Answer[0].data
const noQuotes = pubKeyTxtLookup.substring(1, pubKeyTxtLookup.length - 1).replaceAll("\\n","\n");
if (noQuotes) {
this.localPubKeyDB[pubkeyURL] = noQuotes;
return this.localPubKeyDB[pubkeyURL];
}
}
} catch(err) {
console.error(err);
}
try {
// Try to download as a file.
client.open('GET', "https://" + pubkeyURL, false);
client.send();
if (client.response.includes("-----BEGIN PUBLIC KEY-----")) {
this.localPubKeyDB[pubkeyURL] = client.response;
return this.localPubKeyDB[pubkeyURL];
}
} catch(err) {
console.error(err);
}
try {
let publicKey = this.getGitHubDatabase(pubkeyURL.split('.')[0], pubkeyURL.split('.')[1]);
if (publicKey != undefined && publicKey.includes("-----BEGIN PUBLIC KEY-----")) {
this.localPubKeyDB[pubkeyURL] = publicKey;
return this.localPubKeyDB[pubkeyURL];
} else {
console.error("GitHub Not Found: "+ publicKey);
}
} catch(err) {
console.error(err);
}
return null;
}
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
module.exports = PCF

+ 21
- 235
us.ma.id.html View File

@ -170,89 +170,21 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
<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>
var localPubKeyDB = {};
const EC = elliptic.ec;
const ANS1 = asn1;
// PEM Definitions
const 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()
);
});
const 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 buildPayload(elemArray) {
function getValueArray(elemArray) {
const fields = elemArray.map(function(elemId) {
if (e(elemId) === null) console.log(elemId + "not found");
return encodeURIComponent(e(elemId).value.toUpperCase());
})
return fields.join('/');
}
function buildHashPayload(elemArray) {
const RS = String.fromCharCode(30);
let fields = elemArray.map(function(elemId) {
return e(elemId).value.toUpperCase();
return e(elemId).value;
})
return fields.join(RS);
}
function pad(base32Str) {
switch (base32Str.length % 8) {
case 2: return base32Str + "======";
case 4: return base32Str + "====";
case 5: return base32Str + "===";
case 7: return base32Str + "=";
}
return base32Str;
}
function rmPad(base32Str) {
return base32Str.replaceAll("=", "");
return fields;
}
function verifySignature(pubkey, payload, signatureBase32NoPad, feedback_elem_id) {
try {
// Decoding the public key to get curve algorithm + key itself
const pubk = ECPublicKey.decode(pubkey, 'pem', {label: 'PUBLIC KEY'});
// Get Encryption Algorithm: EC
const algoCode = pubk.algorithm.id.join('.');
// Get Curve Algorithm: secp256k1
const curveCode = pubk.algorithm.curve.join('.');
// Prepare EC with assigned curve
const ec = new EC(curves[curveCode]);
// Load public key from PEM as DER
const key = ec.keyFromPublic(pubk.pub.data, 'der');
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Gets the Base32 enconded, add padding and convert to DER.
const signatureDER = base32.decode.asBytes(pad(signatureBase32NoPad));
// Verifies Signature.
const verified = key.verify(payloadSHA256, signatureDER);
const verified = PCF.verify(pubkey, payload, signatureBase32NoPad, feedback_elem_id)
// Updates screen.
e(feedback_elem_id).innerHTML = "Signature: " + (verified ? "Independently Verified" : "Not Valid");
@ -274,87 +206,18 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
function downloadAndVerify(pubkeyURL, payload, signatureBase32NoPad, feedback_elem_id) {
// Download pubkey to verify
let client = new XMLHttpRequest();
let publicKeyPEM = PCF.getKeyId(pubkeyURL);
if (localPubKeyDB[pubkeyURL]) {
verifySignature(localPubKeyDB[pubkeyURL], payload, signatureBase32NoPad, feedback_elem_id);
return;
}
try {
// Try to download from the DNS TXT record.
client.open('GET', "https://dns.google/resolve?name=" + pubkeyURL + '&type=TXT', false);
client.send();
const jsonResponse = JSON.parse(client.response);
if (jsonResponse.Answer) {
const pubKeyTxtLookup = JSON.parse(client.response).Answer[0].data
const noQuotes = pubKeyTxtLookup.substring(1, pubKeyTxtLookup.length - 1).replaceAll("\\n","\n");
if (noQuotes) {
localPubKeyDB[pubkeyURL] = noQuotes;
verifySignature(noQuotes, payload, signatureBase32NoPad, feedback_elem_id);
return;
}
}
} catch(err) {
e(feedback_elem_id).innerHTML += "Verification Failed: " + err;
console.error(err);
}
try {
// Try to download as a file.
client.open('GET', "https://" + pubkeyURL, false);
client.send();
if (client.response.includes("-----BEGIN PUBLIC KEY-----")) {
localPubKeyDB[pubkeyURL] = client.response;
verifySignature(client.response, payload, signatureBase32NoPad, feedback_elem_id);
return;
}
} catch(err) {
console.error(err);
}
try {
let publicKey = getGitHubDatabase(pubkeyURL.split('.')[0], pubkeyURL.split('.')[1]);
if (publicKey != undefined && publicKey.includes("-----BEGIN PUBLIC KEY-----")) {
localPubKeyDB[pubkeyURL] = publicKey;
verifySignature(publicKey, payload, signatureBase32NoPad, feedback_elem_id);
return;
} else {
console.error("GitHub Not Found: "+ publicKey);
}
} catch(err) {
console.error(err);
if (publicKeyPEM !== null) {
verifySignature(publicKeyPEM, payload, signatureBase32NoPad, feedback_elem_id);
} else {
e(feedback_elem_id).innerHTML += "Verification Failed: Public Key not found.";
}
}
function signAndDisplayQR(elemPref, protocol, priKeyPEM, pubKeyLink, payload) {
// Load Primary Key
const ec_pk = ECPrivateKey.decode(priKeyPEM, 'pem', {label: 'EC PRIVATE KEY'});
// Get Curve Algorithm.
const curveCode = ec_pk.parameters.join('.');
// Prepare EC with assigned curve: secp256k1
const ec = new EC(curves[curveCode]);
// Load Private Key from PEM file converted to DER.
const key = ec.keyFromPrivate(ec_pk.privateKey, 'der');
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Signs, gets a DER
const signatureDER = key.sign(payloadSHA256).toDER();
// Converts DER to Base32 and remove padding.
const signature = rmPad(base32.encode(signatureDER));
// Builds URI
const uri = protocol.toUpperCase()+":"+signature+":"+pubKeyLink.toUpperCase()+":"+payload;
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' }};
@ -386,7 +249,7 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
PDF417.draw(uri, e("pdf-massid-code"), 3, 2, 2);
// Updates screen elements.
e(elemPref+"-result").innerHTML= "<span class='protocol'>"+protocol.toUpperCase()+"</span>:" +
e(elemPref+"-result").innerHTML= schema+":<span class='protocol'>"+type+":"+version+"</span>:" +
"<span class='signature'>" + signature + "</span>" + ":" +
"<span class='pub-key'>" + pubKeyLink.toUpperCase() + "</span>" + ":" +
"<span class='message'>" + payload + "</span><br>";
@ -413,7 +276,7 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
// Verifies URI is valid
downloadAndVerify(pubKeyLink.toUpperCase(), payload, signature, elemPref + "-verified");
downloadAndVerify(pubKeyLink, payload, signature, elemPref + "-verified");
}
function describe(qr) {
@ -488,85 +351,9 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
"qr-zva"
];
signAndDisplayQR("qr-massid", "cred:us.ma.id:3", priKeyPEM, pubKeyLink, buildPayload(massIdArray));
}
function parseQR(qr) {
return [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = qr.split(':');
}
function getPayloadTypes() {
let client = new XMLHttpRequest();
try {
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/main", false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const filesRoot = JSON.parse(client.response).tree
const payloadsDir = filesRoot.find(element => element.path === 'payloads');
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/"+payloadsDir.sha, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const payloads = JSON.parse(client.response).tree.map(x => x.path.replaceAll(".md","").replaceAll(".",":"));
return payloads;
} catch(err) {
console.error(err);
}
signAndDisplayQR("qr-massid", "us.ma.id", "3", priKeyPEM, pubKeyLink, getValueArray(massIdArray));
}
function getTXT(url) {
let client = new XMLHttpRequest();
client.open('GET', url, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
return client.response;
}
function getJSON(url) {
return JSON.parse(getTXT(url));
}
function getGitHubDatabase(id, database) {
const githubTree = "https://api.github.com/repos/Path-Check/paper-cred/git/trees";
const githubBlob = "https://api.github.com/repos/Path-Check/paper-cred/git/blobs";
try {
const rootDir = getJSON(githubTree + "/" + "benefits_limitations").tree
const databasesDir = rootDir.find(element => element.path === 'keys');
if (databasesDir === undefined) {
console.debug("Keys Directory not Found on GitHub");
return;
}
const databases = getJSON(githubTree+"/"+databasesDir.sha).tree
const databaseDir = databases.find(element => element.path === database);
if (databaseDir === undefined) {
console.debug("Database not found on GitHub " + database);
return;
}
const idsFiles = getJSON(githubTree+"/"+databaseDir.sha).tree
const idFile = idsFiles.find(element => element.path === id+'.pem');
if (idFile === undefined) {
console.debug("Id not found on GitHub " + id);
return;
}
const publicKeyPem = atob(getJSON(githubBlob+"/"+idFile.sha).content)
return publicKeyPem;
} catch(err) {
console.error(err);
}
}
function verifyQRCode() {
e("qr-verify-result").innerHTML = "";
e('qr-verify-verified').innerHTML = "";
@ -579,14 +366,15 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
}
try {
parseQR(qr);
PCF.parseURI(qr);
e('qr-verify-verified').innerHTML += "QR was parsed sucessfully!<br><br>";
} catch (err) {
e('qr-verify-verified').innerHTML += "Could not parse string into the URI format.<br>";
console.error(err);
return;
}
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = parseQR(qr);
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = PCF.parseURI(qr);
// Updates screen elements.
e("qr-verify-result").innerHTML = "<span class='protocol'>"+schema+":"+type+":"+version+"</span>:" +
@ -598,13 +386,11 @@ DcJqR5clbAYlO9lHmvb4lsPLZHjugQ==
e('qr-verify-verified').innerHTML += "QR is not a credential: Code must start with CRED instead of "+schema +".<br>";
}
const availablePayloads = getPayloadTypes();
if (!availablePayloads.includes(type.toLowerCase()+":"+version)) {
if (!PCF.getPayloadTypes().includes(type.toLowerCase()+":"+version)) {
e('qr-verify-verified').innerHTML += "Type or version <b>" + type + ":" + version + "</b> was not recognized. Make sure this payload type and version are available on <a href='https://github.com/Path-Check/paper-cred/tree/main/payloads'>GitHub</a> <br>";
}
if (e('qr-verify-verified').innerHTML === "QR was parsed sucessfully!<br><br>") {
if (e('qr-verify-verified').innerHTML.includes("QR was parsed sucessfully!")) {
downloadAndVerify(pubKeyLink, payload, signatureBase32NoPad, 'qr-verify-verified');
} else {
e('qr-verify-verified').innerHTML += "<br>Signature: not verified";


+ 19
- 230
verify.html View File

@ -28,89 +28,22 @@
<script src="js/base32.min.js"></script>
<script src="js/html5-qrcode.min.js"></script>
<script>
var localPubKeyDB = {};
const EC = elliptic.ec;
const ANS1 = asn1;
// PEM Definitions
const 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()
);
});
const 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'};
<script src="js/pcf.js"></script>
<script>
function e(elem) { return document.getElementById(elem); }
function buildPayload(elemArray) {
function getValueArray(elemArray) {
const fields = elemArray.map(function(elemId) {
return encodeURIComponent(e(elemId).value.toUpperCase());
return e(elemId).value;
})
return fields.join('/');
}
function buildHashPayload(elemArray) {
const RS = String.fromCharCode(30);
let fields = elemArray.map(function(elemId) {
return e(elemId).value.toUpperCase();
})
return fields.join(RS);
}
function pad(base32Str) {
switch (base32Str.length % 8) {
case 2: return base32Str + "======";
case 4: return base32Str + "====";
case 5: return base32Str + "===";
case 7: return base32Str + "=";
}
return base32Str;
}
function rmPad(base32Str) {
return base32Str.replaceAll("=", "");
return fields;
}
function verifySignature(pubkey, payload, signatureBase32NoPad, feedback_elem_id) {
try {
// Decoding the public key to get curve algorithm + key itself
const pubk = ECPublicKey.decode(pubkey, 'pem', {label: 'PUBLIC KEY'});
// Get Encryption Algorithm: EC
const algoCode = pubk.algorithm.id.join('.');
// Get Curve Algorithm: secp256k1
const curveCode = pubk.algorithm.curve.join('.');
// Prepare EC with assigned curve
const ec = new EC(curves[curveCode]);
// Load public key from PEM as DER
const key = ec.keyFromPublic(pubk.pub.data, 'der');
// Converts to UTF-8 -> WorldArray -> SHA256 Hash
const payloadSHA256 = CryptoJS.SHA256(payload).toString();
// Gets the Base32 enconded, add padding and convert to DER.
const signatureDER = base32.decode.asBytes(pad(signatureBase32NoPad));
const verified = PCF.verify(pubkey, payload, signatureBase32NoPad, feedback_elem_id)
// Verifies Signature.
const verified = key.verify(payloadSHA256, signatureDER);
// Updates screen.
e(feedback_elem_id).innerHTML = "Signature: " + (verified ? "Independently Verified" : "Not Valid");
} catch(err) {
e(feedback_elem_id).innerHTML += "Signature Verification Failed: " + err;
@ -118,75 +51,13 @@
}
}
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-status');
clearQR('qr-passkey');
clearQR('qr-badge');
clearQR('qr-coupon');
}
function downloadAndVerify(pubkeyURL, payload, signatureBase32NoPad, feedback_elem_id) {
// Download pubkey to verify
let client = new XMLHttpRequest();
if (localPubKeyDB[pubkeyURL]) {
verifySignature(localPubKeyDB[pubkeyURL], payload, signatureBase32NoPad, feedback_elem_id);
return;
}
try {
// Try to download from the DNS TXT record.
client.open('GET', "https://dns.google/resolve?name=" + pubkeyURL + '&type=TXT', false);
client.send();
let publicKeyPEM = PCF.getKeyId(pubkeyURL);
const jsonResponse = JSON.parse(client.response);
if (jsonResponse.Answer) {
const pubKeyTxtLookup = JSON.parse(client.response).Answer[0].data
const noQuotes = pubKeyTxtLookup.substring(1, pubKeyTxtLookup.length - 1).replaceAll("\\n","\n");
if (noQuotes) {
localPubKeyDB[pubkeyURL] = noQuotes;
verifySignature(noQuotes, payload, signatureBase32NoPad, feedback_elem_id);
return;
}
}
} catch(err) {
e(feedback_elem_id).innerHTML += "Verification Failed: " + err;
console.error(err);
}
try {
// Try to download as a file.
client.open('GET', "https://" + pubkeyURL, false);
client.send();
if (client.response.includes("-----BEGIN PUBLIC KEY-----")) {
localPubKeyDB[pubkeyURL] = client.response;
verifySignature(client.response, payload, signatureBase32NoPad, feedback_elem_id);
return;
}
} catch(err) {
console.error(err);
}
try {
let publicKey = getGitHubDatabase(pubkeyURL.split('.')[0], pubkeyURL.split('.')[1]);
if (publicKey != undefined && publicKey.includes("-----BEGIN PUBLIC KEY-----")) {
localPubKeyDB[pubkeyURL] = publicKey;
verifySignature(publicKey, payload, signatureBase32NoPad, feedback_elem_id);
return;
} else {
console.error("GitHub Not Found: "+ publicKey);
}
} catch(err) {
console.error(err);
if (publicKeyPEM !== null) {
verifySignature(publicKeyPEM, payload, signatureBase32NoPad, feedback_elem_id);
} else {
e(feedback_elem_id).innerHTML += "Verification Failed: Public Key not found.";
}
}
@ -194,82 +65,6 @@
return qr.modules.size + "x" + qr.modules.size + " bits " + Math.round((qr.modules.size*qr.modules.size)/8) + " bytes ";
}
function parseQR(qr) {
return [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = qr.split(':');
}
function getPayloadTypes() {
let client = new XMLHttpRequest();
try {
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/main", false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const filesRoot = JSON.parse(client.response).tree
const payloadsDir = filesRoot.find(element => element.path === 'payloads');
client.open('GET', "https://api.github.com/repos/Path-Check/paper-cred/git/trees/"+payloadsDir.sha, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
const payloads = JSON.parse(client.response).tree.map(x => x.path.replaceAll(".md","").replaceAll(".",":"));
return payloads;
} catch(err) {
console.error(err);
}
}
function getTXT(url) {
let client = new XMLHttpRequest();
client.open('GET', url, false);
client.setRequestHeader("Accept", "application/vnd.github.v3+json");
client.send();
return client.response;
}
function getJSON(url) {
return JSON.parse(getTXT(url));
}
function getGitHubDatabase(id, database) {
const githubTree = "https://api.github.com/repos/Path-Check/paper-cred/git/trees";
const githubBlob = "https://api.github.com/repos/Path-Check/paper-cred/git/blobs";
try {
const rootDir = getJSON(githubTree + "/" + "benefits_limitations").tree
const databasesDir = rootDir.find(element => element.path === 'keys');
if (databasesDir === undefined) {
console.debug("Keys Directory not Found on GitHub");
return;
}
const databases = getJSON(githubTree+"/"+databasesDir.sha).tree
const databaseDir = databases.find(element => element.path === database);
if (databaseDir === undefined) {
console.debug("Database not found on GitHub " + database);
return;
}
const idsFiles = getJSON(githubTree+"/"+databaseDir.sha).tree
const idFile = idsFiles.find(element => element.path === id+'.pem');
if (idFile === undefined) {
console.debug("Id not found on GitHub " + id);
return;
}
const publicKeyPem = atob(getJSON(githubBlob+"/"+idFile.sha).content)
return publicKeyPem;
} catch(err) {
console.error(err);
}
}
function verifyQRCode() {
e("qr-verify-result").innerHTML = "";
e('qr-verify-verified').innerHTML = "";
@ -282,20 +77,16 @@
}
try {
parseQR(qr);
PCF.parseURI(qr);
e('qr-verify-verified').innerHTML += "QR was parsed sucessfully!<br><br>";
} catch (err) {
e('qr-verify-verified').innerHTML += "Could not parse string into the URI format.<br>";
console.error(err);
return;
}
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = parseQR(qr);
const encodedFields = payload.split("/");
const decodedFields = encodedFields.map(function(field) {
return decodeURIComponent(field);
})
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = PCF.parseURI(qr);
const decodedFields = PCF.parsePayload(payload);
// Updates screen elements.
e("qr-verify-result").innerHTML = "Type: <span class='protocol'>" + type+":"+version+"</span><br>" +
@ -311,13 +102,11 @@
e('qr-verify-verified').innerHTML += "QR is not a credential: Code must start with CRED instead of "+schema +".<br>";
}
const availablePayloads = getPayloadTypes();
if (!availablePayloads.includes(type.toLowerCase()+":"+version)) {
if (!PCF.getPayloadTypes().includes(type.toLowerCase()+":"+version)) {
e('qr-verify-verified').innerHTML += "Type or version <b>" + type + ":" + version + "</b> was not recognized. Make sure this payload type and version are available on <a href='https://github.com/Path-Check/paper-cred/tree/main/payloads'>GitHub</a> <br>";
}
if (e('qr-verify-verified').innerHTML === "QR was parsed sucessfully!<br><br>") {
if (e('qr-verify-verified').innerHTML.includes("QR was parsed sucessfully!")) {
downloadAndVerify(pubKeyLink, payload, signatureBase32NoPad, 'qr-verify-verified');
} else {
e('qr-verify-verified').innerHTML += "<br>Signature: not verified";
@ -354,8 +143,8 @@
const urlParams = new URLSearchParams(queryString);
const qr = urlParams.get('qr')
if (qr !== "") {
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = parseQR(qr);
if (qr !== "" && qr != null) {
const [schema, type, version, signatureBase32NoPad, pubKeyLink, payload] = PCF.parseURI(qr);
decodedFields = payload.split('/');
const encodedFields = decodedFields.map(function(field) {


Loading…
Cancel
Save