UNPKG

pkijs

Version:

Public Key Infrastructure (PKI) is the basis of how identity and key management is performed on the web today. PKIjs is a pure JavaScript library implementing the formats that are used in PKI applications. It is built on WebCrypto and aspires to make it p

461 lines (411 loc) 17.7 kB
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>PDF signature checking</title> <script type="text/javascript" src="org/pkijs/common.js"></script> <script type="text/javascript" src="org/pkijs/asn1.js"></script> <script type="text/javascript" src="org/pkijs/x509_schema.js"></script> <script type="text/javascript" src="org/pkijs/x509_simpl.js"></script> <script type="text/javascript" src="org/pkijs/cms_schema.js"></script> <script type="text/javascript" src="org/pkijs/cms_simpl.js"></script> <script type="text/javascript" src="pdfjs/pdf.js"></script> <style type="text/css"> body{background:#EFEFEF;font:normal 14px/16px Helvetica, Arial, sans-serif;} .wrapper{ width:600px; margin:50px auto; padding:50px; border:solid 2px #CCC; border-radius:10px; -webkit-border-radius:10px; box-shadow:0 0 12px 3px #CDCDCD; -webkit-box-shadow:0 0 12px 3px #CDCDCD; background:#FFF; } label{ font:bold 16px/20px Helvetica, Arial, sans-serif; margin:0 0 8px; } textarea{ width:500px; border:solid 1px #999; border-radius:5px; -webkit-border-radius:5px; height:340px; font:normal 12px/15px monospace; display:block; margin:0 0 12px; box-shadow:0 0 5px 5px #EFEFEF inset; -webkit-box-shadow:0 0 5px 5px #EFEFEF inset; padding:20px; resize: none; } a{ display:inline-block; padding:5px 15px; background:#ACD0EC; border:solid 1px #4C6181; color:#000; font:normal 14px/16px Helvetica, Arial, sans-serif; } a:hover{ background:#DAEBF8; cursor:pointer; } .header-block { margin-top:30px; font:bold 16px/20px Helvetica, Arial, sans-serif; } .border-block{ border:solid 2px #999; border-radius:5px; -webkit-border-radius:5px; margin:10px 0 0; padding:20px 30px; background:#F0F4FF; } .border-block h2{ margin:0 0 16px; font:bold 22px/24px Helvetica, Arial, sans-serif; } .border-block p{ margin:0 0 12px; } .border-block p .type{ font-weight:bold; display:inline-block; width:176px; } .border-block .two-col{ overflow:hidden; margin:0 0 16px; } .border-block .two-col .subject{ width:180px; font-weight:bold; margin:0 0 12px; float:left; } .border-block .two-col #cms-signed-attributes{ margin:0; padding:0; float:left; list-style:none; } .border-block .two-col #cms-signed-attributes li p{ margin:0; } .border-block .two-col #cms-signed-attributes li p span{ width:40px; display:inline-block; margin:0 0 5px; } .border-block .two-col #cms-signed-exten{ overflow:hidden; padding:0 0 0 17px; margin:0; list-style-type:square; } table { border:solid; border-collapse:collapse; border-color:black; } th { text-align:center; background: #ccc; padding: 5px; border: 1px solid black; } td { padding: 5px; border: 1px solid black; } </style> <script> //********************************************************************************* var trustedCertificates = new Array(); // Array of org.pkijs.simpl.CERT //********************************************************************************* function verifyPDFSignature(buffer) { try { var view = new Uint8Array(buffer); var pdf = new PDFDocument(null, view, null); pdf.parseStartXRef(); pdf.parse(); var acroForm = pdf.xref.root.get("AcroForm"); if(typeof acroForm === "undefined") throw new Error("The PDF has no signature!"); var fields = acroForm.get("Fields"); if(isRef(fields[0]) === false) throw new Error("Wrong structure of PDF!"); var sigField = pdf.xref.fetch(fields[0]); var sigFieldType = sigField.get("FT"); if((typeof sigFieldType === "undefined") || (sigFieldType.name !== "Sig")) throw new Error("Wrong structure of PDF!"); var v = sigField.get("V"); var byteRange = v.get("ByteRange"); var contents = v.get("Contents"); var contentLength = contents.length; var contentBuffer = new ArrayBuffer(contentLength); var contentView = new Uint8Array(contentBuffer); for(var i = 0; i < contentLength; i++) contentView[i] = contents.charCodeAt(i); var sequence = Promise.resolve(); var asn1 = org.pkijs.fromBER(contentBuffer); var cms_content_simp = new org.pkijs.simpl.CMS_CONTENT_INFO({ schema: asn1.result }); var cms_signed_simp = new org.pkijs.simpl.CMS_SIGNED_DATA({ schema: cms_content_simp.content }); var signedDataBuffer = new ArrayBuffer(byteRange[1] + byteRange[3]); var signedDataView = new Uint8Array(signedDataBuffer); var count = 0; for(var i = byteRange[0]; i < (byteRange[0] + byteRange[1]); i++, count++) signedDataView[count] = view[i]; for(var j = byteRange[2]; j < (byteRange[2] + byteRange[3]); j++, count++) signedDataView[count] = view[j]; sequence = sequence.then( function() { return cms_signed_simp.verify({ signer: 0, data: signedDataBuffer, trusted_certs: trustedCertificates }); } ); if("signedAttrs" in cms_signed_simp.signerInfos[0]) { var crypto = org.pkijs.getCrypto(); if(typeof crypto == "undefined") throw new Error("WebCrypto extension is not installed"); var sha_algorithm = ""; switch(cms_signed_simp.signerInfos[0].digestAlgorithm.algorithm_id) { case "1.3.14.3.2.26": sha_algorithm = "sha-1"; break; case "2.16.840.1.101.3.4.2.1": sha_algorithm = "sha-256"; break; case "2.16.840.1.101.3.4.2.2": sha_algorithm = "sha-384"; break; case "2.16.840.1.101.3.4.2.3": sha_algorithm = "sha-512"; break; default: throw new Error("Unknown hashing algorithm"); }; sequence = sequence.then( function(result) { if(result === false) return new Promise(function(resolve, reject) { reject("Signature verification failed"); }); return crypto.digest({ name: sha_algorithm }, new Uint8Array(signedDataBuffer)); }, function(error) { return new Promise(function(resolve, reject) { reject("Error during signature verification"); }); } ); sequence = sequence.then( function(result) { var messageDigest = new ArrayBuffer(0); for(var j = 0; j < cms_signed_simp.signerInfos[0].signedAttrs.attributes.length; j++) { if(cms_signed_simp.signerInfos[0].signedAttrs.attributes[j].attrType === "1.2.840.113549.1.9.4") { messageDigest = cms_signed_simp.signerInfos[0].signedAttrs.attributes[j].attrValues[0].value_block.value_hex; break; } } if(messageDigest.byteLength === 0) return new Promise(function(resolve, reject) { reject("No signed attribute \"MessageDigest\""); }); var view1 = new Uint8Array(messageDigest); var view2 = new Uint8Array(result); if(view1.length !== view2.length) return new Promise(function(resolve, reject) { reject("Hash is not correct"); }); for(var i = 0; i < view1.length; i++) { if(view1[i] !== view2[i]) return new Promise(function(resolve, reject) { reject("Hash is not correct"); }); } }, function(error) { return new Promise(function(resolve, reject) { reject("Error during hashing data"); }); } ); } sequence.then( function(result) { if(typeof result !== "undefined") { if(result === false) { alert("PDF verification failed!") return; } } alert("PDF successfully verified!") }, function(error) { alert("Error: " + error); } ); } catch(err) { alert(err); } } //********************************************************************************* function parseCABundle(buffer) { // #region Initial variables var base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var startChars = "-----BEGIN CERTIFICATE-----"; var endChars = "-----END CERTIFICATE-----"; var endLineChars = "\r\n"; var view = new Uint8Array(buffer); var waitForStart = false; var middleStage = true; var waitForEnd = false; var waitForEndLine = false; var started = false; var certBodyEncoded = ""; // #endregion for(var i = 0; i < view.length; i++) { if(started === true) { if(base64Chars.indexOf(String.fromCharCode(view[i])) !== (-1)) certBodyEncoded = certBodyEncoded + String.fromCharCode(view[i]); else { if(String.fromCharCode(view[i]) === '-') { // #region Decoded trustedCertificates var asn1 = org.pkijs.fromBER(stringToArrayBuffer(window.atob(certBodyEncoded))); try { trustedCertificates.push(new org.pkijs.simpl.CERT({ schema: asn1.result })); } catch(ex) { //alert("Wrong certificate format"); //return; } // #endregion // #region Set all "flag variables" certBodyEncoded = ""; started = false; waitForEnd = true; // #endregion } } } else { if(waitForEndLine === true) { if(endLineChars.indexOf(String.fromCharCode(view[i])) === (-1)) { waitForEndLine = false; if(waitForEnd === true) { waitForEnd = false; middleStage = true; } else { if(waitForStart === true) { waitForStart = false; started = true; certBodyEncoded = certBodyEncoded + String.fromCharCode(view[i]); } else middleStage = true; } } } else { if(middleStage === true) { if(String.fromCharCode(view[i]) === "-") { if((i === 0) || ((String.fromCharCode(view[i - 1]) === "\r") || (String.fromCharCode(view[i - 1]) === "\n"))) { middleStage = false; waitForStart = true; } } } else { if(waitForStart === true) { if(startChars.indexOf(String.fromCharCode(view[i])) === (-1)) waitForEndLine = true; } else { if(waitForEnd === true) { if(endChars.indexOf(String.fromCharCode(view[i])) === (-1)) waitForEndLine = true; } } } } } } } //********************************************************************************* function handleFileBrowse(evt) { var temp_reader = new FileReader(); var current_files = evt.target.files; temp_reader.onload = function(event) { verifyPDFSignature(event.target.result); }; temp_reader.readAsArrayBuffer(current_files[0]); } //********************************************************************************* function handleCABundle(evt) { var temp_reader = new FileReader(); var current_files = evt.target.files; temp_reader.onload = function(event) { parseCABundle(event.target.result); }; temp_reader.readAsArrayBuffer(current_files[0]); } //********************************************************************************* </script> </head> <body> <div id="cert_div" class="wrapper"> <p> <label for="ca_bundle">Select CA bundle file:</label> <input type="file" id="ca_bundle" title="Trusted certs" /> </p> <p> <label for="pdf_file">Select PDF file:</label> <input type="file" id="pdf_file" title="PDF to check" /> </p> </div> <script> document.getElementById('pdf_file').addEventListener('change', handleFileBrowse, false); document.getElementById('ca_bundle').addEventListener('change', handleCABundle, false); </script> </body> </html>