How to create a digital signing solution with only JavaScript

By bozhobg

It is sometimes required to have the user sign a text in order to certify that he is the one who has done the operation. For example, in an e-banking software, the user might have to sign a text describing the transaction (“Transfer 300 dollars to IBAN xxxxxxxxx”), or sign a request for a governmental eService. This could be achieved by a java-applet, but as JRE owners are not a majority, it is preferable to use other ways.

Of course, the preconditions are, that the user has a digital signature, issued by a CA, and has followed the CA’s manual for installing the certificate in a browser. If these steps are not completed successfully, the solution below wouldn’t work.

Also, note that this uses PKCS7 (java developers: use bouncy castle to verify it), instead of the XAdES standard. Internet Explorer has support for XAdES, but FireFox doesn’t.

Let’s see a simple HTML page that should sign a given text:


<script src="sign.js" type="text/javascript"></script>

<input id="text" type="text" />
<input onclick="signDigest(document.getElementById('text').value);" type="button" value="Sign" />

and then the JavaScript itself:


function signDigest(text)
{
if(window.event)
window.event.cancelBubble = true;

var dest = sign(text); //TODO
alert(dest);
return dest;
}

// CAPICOM constants
var CAPICOM_STORE_OPEN_READ_ONLY = 0;
var CAPICOM_CURRENT_USER_STORE = 2;
var CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0;
var CAPICOM_CERTIFICATE_FIND_EXTENDED_PROPERTY = 6;
var CAPICOM_CERTIFICATE_FIND_TIME_VALID = 9;
var CAPICOM_CERTIFICATE_FIND_KEY_USAGE = 12;
var CAPICOM_DIGITAL_SIGNATURE_KEY_USAGE = 0x00000080;
var CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME = 0;
var CAPICOM_INFO_SUBJECT_SIMPLE_NAME = 0;
var CAPICOM_ENCODE_BASE64 = 0;
var CAPICOM_E_CANCELLED = -2138568446;
var CERT_KEY_SPEC_PROP_ID = 6;

function IsCAPICOMInstalled()
{
if(typeof(oCAPICOM) == "object")
{
if( (oCAPICOM.object != null) )
{
// We found CAPICOM!
return true;
}
}
}

function FindCertificateByHash()
{

try
{
// instantiate the CAPICOM objects
var MyStore = new ActiveXObject("CAPICOM.Store");
// open the current users personal certificate store
MyStore.Open(CAPICOM_CURRENT_USER_STORE, "My", CAPICOM_STORE_OPEN_READ_ONLY);

// find all of the certificates that have the specified hash
var FilteredCertificates = MyStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SHA1_HASH, strUserCertigicateThumbprint);

var Signer = new ActiveXObject("CAPICOM.Signer");
Signer.Certificate = FilteredCertificates.Item(1);
return Signer;

// Clean Up
MyStore = null;
FilteredCertificates = null;
}
catch (e)
{
if (e.number != CAPICOM_E_CANCELLED)
{
return new ActiveXObject("CAPICOM.Signer");
}
}
}

function sign(src)
{
if(window.crypto &amp;&amp; window.crypto.signText)
return sign_NS(src);

return sign_IE(src);
}

function sign_NS(src)
{
var s = crypto.signText(src, "ask" );
return s;
}

function sign_IE(src)
{
try
{
// instantiate the CAPICOM objects
var SignedData = new ActiveXObject("CAPICOM.SignedData");
var TimeAttribute = new ActiveXObject("CAPICOM.Attribute");

// Set the data that we want to sign
SignedData.Content = src;
var Signer = FindCertificateByHash();

// Set the time in which we are applying the signature
var Today = new Date();
TimeAttribute.Name = CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
TimeAttribute.Value = Today.getVarDate();
Today = null;
Signer.AuthenticatedAttributes.Add(TimeAttribute);

// Do the Sign operation
var szSignature = SignedData.Sign(Signer, true, CAPICOM_ENCODE_BASE64);
return szSignature;
}
catch (e)
{
if (e.number != CAPICOM_E_CANCELLED)
{
alert("An error occurred when attempting to sign the content, the errot was: " + e.description);
}
}
return "";
}

And that should do the stuff – the signed text can be sent to the server, where it can be verified (in case, of course, the server has the public part of the user’s certificate)

P.S. One important note when verifying afterward – Internet Explorer uses UnicodeLittleUnmarked (UTF-16LE) to encode the signed data, before signing it. So when verifying, use this encoding.

25 Responses to “How to create a digital signing solution with only JavaScript”

  1. EllisGL Says:

    Can we see a demo of this?

  2. bozhobg Says:

    Well, it’s pretty much all in the code above – just copy-paste it in an .html file and you have the demo. But have in mind that you must have a real signature, otherwise browsers give errors.

  3. Kristof Says:

    IE gives this error: “An error occurred when attempting to sign the content, the error was: Automation server can’t create object”

    Ever seen such? :)

  4. Bozho Says:

    Yes. In order to sign successfully (with both IE and Firefox) you need a installed smart card with the digital signature. Self-signed certificates can’t be used with the browsers to sign content. (don’t ask me why :) )

  5. B J Says:

    Hi, this jscript is encrypting some given text “Hello” using CAPICOM sertificate?? Ok, so if this is true i would like to get public Key from sertificate, and later use this public key to verify authetntication of used sertificate for encryption(i will encrypt the same text “Hello” but this time with public key and compare both encriptions). Is there any chance for that???

    Thanks

  6. Bozho Says:

    I didn’t quite get your idea, but getting the public key from the encrypted message will be a server-side action, about which I will blog tomorrow (stay tuned). Then you can use it in whatever way you need.

  7. How to obtain signer’s details from a JavaScript signed data « Bozho’s Weblog Says:

    [...] to obtain signer’s details from a JavaScript signed data By bozhobg In a previous post I described how to sign data with only javascript. Now, this data should be used on the server side [...]

  8. bozhobg Says:

    Check this out – http://bozhobg.wordpress.com/2009/07/02/how-to-obtain-signers-details-from-a-javascript-signed-data/

  9. B J Says:

    Hi,

    Thanks for your quick replay. My question was how to get public key from CAPICOM certificate.Later use that key to decrypt signed(encrypted) text, and make comparison between original and decrypted text, wich should have some function like verification.

    P.S is there any chance to convert your Java code from your newest blog to C#?

    Thanks

  10. bozhobg Says:

    Well, I don’t have enough experience with C# to be able to do so – furthermore, I’m not even sure bouncy castle have C# libraries, so you have to ‘translate’ it yourself, and find the appropriate libraries :)

  11. B J Says:

    hi me again :)

    I can’t understand why on some PCs i am able to get Certificate and sign text and on the other PCs i am get error (Error in reading DLL)????

    when i was Debuging I found that this error occurs in the next two lines.

    var SignedData = new ActiveXObject(“CAPICOM.SignedData”);
    var TimeAttribute = new ActiveXObject(“CAPICOM.Attribute”);

    Thanks for your time and previous responses.

  12. B J Says:

    I forget to say that all PCs in my network have certificate by same Issuer.

  13. Bozho Says:

    Hm, that would probably be due to unsuccessfully installed smart-card software (Charismatics, for instance). Check the installation procedure from the signature vendor.

  14. B J Says:

    Thanks i instaled CAPICOM on all PC’s where i was not able to get theirs certificate, and that solves my problem, now it’s working exelent.
    Thanks again for your help.

  15. Sushil Says:

    This is good but i need only Secure email signature from etoken can u tell me the javascript code for that

  16. Bozho Says:

    Whether it is SmartCard, eToken or self-signed certificate , it doesn’t matter, as long as the browser is set-up to locate them.

  17. Ramba Says:

    Hi.

    Thanks for this valuable article.I wanted to know the signed output length of your code.I am getting morethen 3000 char ,even for a 15 char input.

    Thanks and regards..

    Ramba

  18. Bozho Says:

    Yes, this is normal. The whole certificate data is encoded in base64, so it’s fine.

  19. Ramba Says:

    Thank you for your response.I wanted to convert this output into pdf417 barcode ,2D. Pdf417 is supproting upto 1800 ascii character only.

    Is there any way to compress the output to reduce the number of characters?.Or please suggest alternate for capicom ,to sign at client side.

    Thanks and Regards.
    Ramba.

  20. Bozho Says:

    What information should the barcode contain?
    You can extract whichever part of the PKCS7 you wish – check this post: http://bozhobg.wordpress.com/2009/07/02/how-to-obtain-signers-details-from-a-javascript-signed-data/

  21. Ramba Says:

    Thank you for your reply.

    The barcode should contain the signature.ie SignedData.Sign methods output.I am implementing in asp.net application.The content of the barcode should be used for signature verification in future.

    Regards

    Ramba

  22. Bozho Says:

    Well, then extract the publicKey and put it in the barcode. Should suffice.
    But the PKCS7 contains much more information, hence the big size.

  23. Ramba Says:

    Thank you,

    I will do in that way.

    Regards,

    Ramba

  24. Ramba Says:

    Thank You,

    I will do based on your suggession..

    Regards,

    Ramba

  25. Sushil Agrawal Says:

    Thanks you for Reply
    I created code for reading etoken code is working fine on local host but when i access page from IIS this is not working What i have to do for that.

Leave a Reply