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
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>