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

492 lines (440 loc) 19.7 kB
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>S/MIME 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="js/mimefuncs.js"></script> <script type="text/javascript" src="js/addressparser.js"></script> <script type="text/javascript" src="js/mimeparser-tzabbr.js"></script> <script type="text/javascript" src="js/mimeparser.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 10px/13px 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 root certificates from "CA Bundle" //************************************************************************************** function concatArray(inputBuffer, inputArray) { var inputView = new Uint8Array(inputBuffer); var returnBuffer = new ArrayBuffer(inputView.length + inputArray.length); var outputView = new Uint8Array(returnBuffer); for(var i = 0; i < inputView.length; i++) outputView[i] = inputView[i]; for(var j = 0; j < inputArray.length; j++) outputView[j + inputView.length] = inputArray[j]; return returnBuffer; } //************************************************************************************** function stringToArrayBuffer(str) { var strLength = str.length; var resultBuffer = new ArrayBuffer(str.length); var resultView = new Uint8Array(resultBuffer); for(var i = 0; i < strLength; i++) resultView[i] = str.charCodeAt(i); return resultBuffer; } //************************************************************************************** function verifySMIME() { var parser = new MimeParser(); parser.write(document.getElementById("smime_message").value); parser.end(); if(("_childNodes" in parser.node) || (parser.node._childNodes.length !== 2)) { var lastNode = parser.getNode("2"); if((lastNode.contentType.value === "application/x-pkcs7-signature") || (lastNode.contentType.value === "application/pkcs7-signature")) { var signedBuffer = concatArray(new ArrayBuffer(0), lastNode.content); var asn1 = org.pkijs.fromBER(signedBuffer); 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 = stringToArrayBuffer(parser.nodes.node1.raw.replace(/\n/g, "\r\n")); var sequence = Promise.resolve(); sequence = sequence.then( function() { return cms_signed_simp.verify({ signer: 0, data: signedDataBuffer, trusted_certs: trustedCertificates }); } ); if("signedAttrs" in cms_signed_simp.signerInfos[0]) { // #region Verify hash of S/MIME message var crypto = org.pkijs.getCrypto(); if(typeof crypto == "undefined") throw new Error("WebCrypto extension is not installed"); // #region Find correct hashing algorithm 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"); }; // #endregion // #region Hashing data 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) { var message = "Error during signature verification: " + ((error instanceof Object) ? error.result_message : error); if(trustedCertificates.length === 0) message = message + "\n\nPlease load CA bundle file and try again"; reject(message); }); } ); sequence = sequence.then( function(result) { // #region Find signed attribute with message digest 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\""); }); // #endregion // #region Compare two ArrayBuffers 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"); }); } // #endregion } ); // #endregion // #endregion } sequence.then( function(result) { if(typeof result !== "undefined") { if(result === false) { alert("S/MIME message verification failed!") return; } } alert("S/MIME message successfully verified!") }, function(error) { alert("Error: " + error); } ); } } else alert("No child nodes!"); parser.onend = function() { var node = parser.getNode(); alert("Parsing finished. Length: " + node.headers['subject'] ); }; } //************************************************************************************** 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 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]); } //********************************************************************************* function handleMIMEFile(evt) { var temp_reader = new FileReader(); var current_files = evt.target.files; temp_reader.onload = function(event) { document.getElementById("smime_message").value = String.fromCharCode.apply(null, new Uint8Array(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> <div class="border-block"> <p> <label for="mime_file">Select S/MIME message file:</label> <input type="file" id="mime_file" title="S/MIME message" /> </p> <p>OR</p> <p> <label for="smime_message" style="font-weight:bold">Put S/MIME signed message here:</label> <textarea id="smime_message"></textarea> </p> <input type="button" value="Verify" onclick="verifySMIME()" /> </div> </div> <script> document.getElementById('ca_bundle').addEventListener('change', handleCABundle, false); document.getElementById('mime_file').addEventListener('change', handleMIMEFile, false); </script> </body> </html>