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
482 lines (414 loc) • 19.5 kB
HTML
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>How to create new CMS Signed data</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>
<style type="text/css">
*
{
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow:auto;
}
html,body
{
width:100%;
max-width:100%;
margin:0;
padding:0;
border-style:none;
}
body *
{
font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
}
textarea
{
width:100%;
height:300px;
resize:none;
}
div#output_div
{
position:relative;
display:block;
left:50%;
width:700px;
margin-left:-350px;
margin-top:20px;
margin-bottom:10px;
}
</style>
<script type="text/javascript">
//*********************************************************************************
function formatPEM(pem_string)
{
/// <summary>Format string in order to have each line with length equal to 63</summary>
/// <param name="pem_string">String to format</param>
var string_length = pem_string.length;
var result_string = "";
for(var i = 0, count = 0; i < string_length; i++, count++)
{
if(count > 63)
{
result_string = result_string + "\r\n";
count = 0;
}
result_string = result_string + pem_string[i];
}
return result_string;
}
//*********************************************************************************
function create_new_signed_data(buffer)
{
// #region Initial variables
var sequence = Promise.resolve();
var cert_simpl = new org.pkijs.simpl.CERT();
var cms_signed_simpl;
var publicKey;
var privateKey;
var hash_algorithm = "sha-1";
var hash_algorithm_oid = "1.3.14.3.2.26";
var signature_algorithm = "1.2.840.113549.1.1.5";
var hash_option = document.getElementById("hash_alg").value;
switch(hash_option)
{
case "alg_SHA1":
hash_algorithm = "sha-1";
hash_algorithm_oid = "1.3.14.3.2.26";
signature_algorithm = "1.2.840.113549.1.1.5";
break;
case "alg_SHA256":
hash_algorithm = "sha-256";
hash_algorithm_oid = "2.16.840.1.101.3.4.2.1";
signature_algorithm = "1.2.840.113549.1.1.11";
break;
case "alg_SHA384":
hash_algorithm = "sha-384";
hash_algorithm_oid = "2.16.840.1.101.3.4.2.2";
signature_algorithm = "1.2.840.113549.1.1.12";
break;
case "alg_SHA512":
hash_algorithm = "sha-512";
hash_algorithm_oid = "2.16.840.1.101.3.4.2.3";
signature_algorithm = "1.2.840.113549.1.1.13";
break;
default:;
}
// #endregion
// #region Get a "crypto" extension
var crypto = org.pkijs.getCrypto();
if(typeof crypto == "undefined")
{
alert("No WebCrypto extension found");
return;
}
// #endregion
// #region Put a static values
cert_simpl.version = 2;
cert_simpl.serialNumber = new org.pkijs.asn1.INTEGER({ value: 1 });
cert_simpl.issuer.types_and_values.push(new org.pkijs.simpl.ATTR_TYPE_AND_VALUE({
type: "2.5.4.6", // Country name
value: new org.pkijs.asn1.PRINTABLESTRING({ value: "RU" })
}));
cert_simpl.issuer.types_and_values.push(new org.pkijs.simpl.ATTR_TYPE_AND_VALUE({
type: "2.5.4.3", // Common name
value: new org.pkijs.asn1.BMPSTRING({ value: "Test" })
}));
cert_simpl.subject.types_and_values.push(new org.pkijs.simpl.ATTR_TYPE_AND_VALUE({
type: "2.5.4.6", // Country name
value: new org.pkijs.asn1.PRINTABLESTRING({ value: "RU" })
}));
cert_simpl.subject.types_and_values.push(new org.pkijs.simpl.ATTR_TYPE_AND_VALUE({
type: "2.5.4.3", // Common name
value: new org.pkijs.asn1.BMPSTRING({ value: "Test" })
}));
cert_simpl.notBefore.value = new Date(2013, 01, 01);
cert_simpl.notAfter.value = new Date(2016, 01, 01);
cert_simpl.extensions = new Array(); // Extensions are not a part of certificate by default, it's an optional array
// #region "BasicConstraints" extension
var basic_constr = new org.pkijs.simpl.x509.BasicConstraints({
cA: true,
pathLenConstraint: 3
});
cert_simpl.extensions.push(new org.pkijs.simpl.EXTENSION({
extnID: "2.5.29.19",
critical: false,
extnValue: basic_constr.toSchema().toBER(false),
parsedValue: basic_constr // Parsed value for well-known extensions
}));
// #endregion
// #region "KeyUsage" extension
var bit_array = new ArrayBuffer(1);
var bit_view = new Uint8Array(bit_array);
bit_view[0] = bit_view[0] | 0x02; // Key usage "cRLSign" flag
bit_view[0] = bit_view[0] | 0x04; // Key usage "keyCertSign" flag
var key_usage = new org.pkijs.asn1.BITSTRING({ value_hex: bit_array });
cert_simpl.extensions.push(new org.pkijs.simpl.EXTENSION({
extnID: "2.5.29.15",
critical: false,
extnValue: key_usage.toBER(false),
parsedValue: key_usage // Parsed value for well-known extensions
}));
// #endregion
cert_simpl.signatureAlgorithm.algorithm_id = signature_algorithm;
cert_simpl.signature.algorithm_id = cert_simpl.signatureAlgorithm.algorithm_id; // Must be the same value
// #endregion
// #region Create a new key pair
sequence = sequence.then(
function()
{
return crypto.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: hash_algorithm } }, true, ["sign", "verify"]);
}
);
// #endregion
// #region Store new key in an interim variables
sequence = sequence.then(
function(keyPair)
{
publicKey = keyPair.publicKey;
privateKey = keyPair.privateKey;
},
function(error)
{
alert("Error during key generation: " + error);
}
);
// #endregion
// #region Exporting public key into "subjectPublicKeyInfo" value of certificate
sequence = sequence.then(
function()
{
return cert_simpl.subjectPublicKeyInfo.importKey(publicKey);
}
);
// #endregion
// #region Signing final certificate
sequence = sequence.then(
function()
{
return cert_simpl.sign(privateKey);
},
function(error)
{
alert("Error during exporting public key: " + error);
}
);
// #endregion
// #region Encode and store certificate
sequence = sequence.then(
function()
{
var cert_simpl_encoded = cert_simpl.toSchema(true).toBER(false);
var cert_simpl_string = String.fromCharCode.apply(null, new Uint8Array(cert_simpl_encoded));
var result_string = "-----BEGIN CERTIFICATE-----\r\n";
result_string = result_string + formatPEM(window.btoa(cert_simpl_string));
result_string = result_string + "\r\n-----END CERTIFICATE-----\r\n";
document.getElementById("new_signed_data").innerHTML = result_string;
alert("Certificate created successfully!");
},
function(error)
{
alert("Error during signing: " + error);
}
);
// #endregion
// #region Exporting private key
sequence = sequence.then(
function()
{
return crypto.exportKey("pkcs8", privateKey);
}
);
// #endregion
// #region Store exported key on Web page
sequence = sequence.then(
function(result)
{
var private_key_string = String.fromCharCode.apply(null, new Uint8Array(result));
var result_string = document.getElementById("new_signed_data").innerHTML;
result_string = result_string + "\r\n-----BEGIN PRIVATE KEY-----\r\n";
result_string = result_string + formatPEM(window.btoa(private_key_string));
result_string = result_string + "\r\n-----END PRIVATE KEY-----\r\n";
document.getElementById("new_signed_data").innerHTML = result_string;
alert("Private key exported successfully!");
},
function(error)
{
alert("Error during exporting of private key: " + error);
}
);
// #endregion
if(document.getElementById("add_ext").checked)
{
// #region Create a message digest
sequence = sequence.then(
function()
{
return crypto.digest({ name: hash_algorithm }, new Uint8Array(buffer));
}
);
// #endregion
// #region Combine all signed extensions
sequence = sequence.then(
function(result)
{
var signed_attr = new Array();
signed_attr.push(new org.pkijs.simpl.cms.Attribute({
attrType: "1.2.840.113549.1.9.3",
attrValues: [
new org.pkijs.asn1.OID({ value: "1.2.840.113549.1.7.1" })
]
})); // contentType
signed_attr.push(new org.pkijs.simpl.cms.Attribute({
attrType: "1.2.840.113549.1.9.5",
attrValues: [
new org.pkijs.asn1.UTCTIME({ value_date: new Date() })
]
})); // signingTime
signed_attr.push(new org.pkijs.simpl.cms.Attribute({
attrType: "1.2.840.113549.1.9.4",
attrValues: [
new org.pkijs.asn1.OCTETSTRING({ value_hex: result })
]
})); // messageDigest
return signed_attr;
}
);
// #endregion
}
// #region Initialize CMS Signed Data structures and sign it
sequence = sequence.then(
function(result)
{
cms_signed_simpl = new org.pkijs.simpl.CMS_SIGNED_DATA({
version: 1,
digestAlgorithms: [
new org.pkijs.simpl.ALGORITHM_IDENTIFIER({
algorithm_id: hash_algorithm_oid,
algorithm_params: new org.pkijs.asn1.NULL()
}) // SHA-1
],
encapContentInfo: new org.pkijs.simpl.cms.EncapsulatedContentInfo({
eContentType: "1.2.840.113549.1.7.1", // "data" content type
eContent: new org.pkijs.asn1.OCTETSTRING({ value_hex: buffer })
}),
signerInfos: [
new org.pkijs.simpl.CMS_SIGNER_INFO({
version: 1,
sid: new org.pkijs.simpl.cms.IssuerAndSerialNumber({
issuer: cert_simpl.issuer,
serialNumber: cert_simpl.serialNumber
}),
digestAlgorithm: new org.pkijs.simpl.ALGORITHM_IDENTIFIER({
algorithm_id: hash_algorithm_oid,
algorithm_params: new org.pkijs.asn1.NULL()
}), // SHA-1
signatureAlgorithm: new org.pkijs.simpl.ALGORITHM_IDENTIFIER({
algorithm_id: "1.2.840.113549.1.1.1",
algorithm_params: new org.pkijs.asn1.NULL()
}), // RSA (PKCS #1 v1.5) key transport algorithm
})
],
certificates: [cert_simpl]
});
if(document.getElementById("add_ext").checked)
{
cms_signed_simpl.signerInfos[0].signedAttrs = new org.pkijs.simpl.cms.SignedUnsignedAttributes({
type: 0,
attributes: result
});
}
return cms_signed_simpl.sign(privateKey, 0);
}
);
// #endregion
sequence.then(
function(result)
{
var cms_signed_schema = cms_signed_simpl.toSchema(true);
var cms_content_simp = new org.pkijs.simpl.CMS_CONTENT_INFO({
contentType: "1.2.840.113549.1.7.2",
content: cms_signed_schema
});
var cms_signed_schema = cms_content_simp.toSchema(true);
// #region Make length of some elements in "indefinite form"
cms_signed_schema.len_block.is_indefinite_form = true;
var block1 = cms_signed_schema.value_block.value[1];
block1.len_block.is_indefinite_form = true;
var block2 = block1.value_block.value[0];
block2.len_block.is_indefinite_form = true;
var block3 = block2.value_block.value[2];
block3.len_block.is_indefinite_form = true;
block3.value_block.value[1].len_block.is_indefinite_form = true;
block3.value_block.value[1].value_block.value[0].len_block.is_indefinite_form = true;
// #endregion
var cms_signed_encoded = cms_signed_schema.toBER(false);
// #region Convert ArrayBuffer to String
var signed_data_string = "";
var view = new Uint8Array(cms_signed_encoded);
for(var i = 0; i < view.length; i++)
signed_data_string = signed_data_string + String.fromCharCode(view[i]);
// #endregion
var result_string = document.getElementById("new_signed_data").innerHTML;
result_string = result_string + "\r\n-----BEGIN CMS-----\r\n";
result_string = result_string + formatPEM(window.btoa(signed_data_string));
result_string = result_string + "\r\n-----END CMS-----\r\n\r\n";
document.getElementById("new_signed_data").innerHTML = result_string;
alert("CMS Signed Data created successfully!");
},
function(error)
{
alert("Erorr during signing of CMS Signed Data: " + error);
}
);
}
//*********************************************************************************
function handleFileBrowse(evt)
{
var temp_reader = new FileReader();
var current_files = evt.target.files;
temp_reader.onload =
function(event)
{
create_new_signed_data(event.target.result);
};
temp_reader.readAsArrayBuffer(current_files[0]);
}
//*********************************************************************************
</script>
</head>
<body">
<div id="output_div">
<p>
<label for="input_file" style="font-weight:bold">Select a file to make signature for:</label>
<input type="file" id="input_file" title="Input file" />
</p>
<p>
<label for="add_ext" style="font-weight:bold">Add signed extensions in SignerInfo:</label>
<input type="checkbox" id="add_ext" value="0" />
</p>
<p>
<label for="hash_alg" style="font-weight:bold">Hashing algorithm:</label>
<select id="hash_alg">
<option value="alg_SHA1">SHA-1</option>
<option value="alg_SHA256">SHA-256</option>
<option value="alg_SHA384">SHA-384</option>
<option value="alg_SHA512">SHA-512</option>
</select>
</p>
<label for="new_signed_data" style="font-weight:bold">CMS Signed Data + BASE-64 encoded new certificate + PKCS#8 private key:</label>
<textarea id="new_signed_data">< New CSM signed data + new encoded certificate + PKCS#8 exported private key will be stored here ></textarea>
</div>
<script>
document.getElementById('input_file').addEventListener('change', handleFileBrowse, false);
</script>
</body>
</html>