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 && 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.
May 3, 2009 at 3:34 am |
Can we see a demo of this?
May 3, 2009 at 6:53 am |
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.
June 26, 2009 at 2:10 pm |
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?
June 27, 2009 at 6:56 am |
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
)
June 30, 2009 at 7:35 am |
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
July 1, 2009 at 8:17 pm |
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.
July 2, 2009 at 9:23 am |
[...] 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 [...]
July 2, 2009 at 9:25 am |
Check this out – http://bozhobg.wordpress.com/2009/07/02/how-to-obtain-signers-details-from-a-javascript-signed-data/
July 3, 2009 at 10:37 am |
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
July 3, 2009 at 12:35 pm |
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
July 10, 2009 at 12:15 pm |
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.
July 10, 2009 at 12:20 pm |
I forget to say that all PCs in my network have certificate by same Issuer.
July 10, 2009 at 4:28 pm |
Hm, that would probably be due to unsuccessfully installed smart-card software (Charismatics, for instance). Check the installation procedure from the signature vendor.
July 14, 2009 at 8:10 am |
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.
November 4, 2009 at 7:51 am |
This is good but i need only Secure email signature from etoken can u tell me the javascript code for that
November 4, 2009 at 2:51 pm |
Whether it is SmartCard, eToken or self-signed certificate , it doesn’t matter, as long as the browser is set-up to locate them.
November 11, 2009 at 10:45 am |
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
November 11, 2009 at 11:40 am |
Yes, this is normal. The whole certificate data is encoded in base64, so it’s fine.