You can a sign documents using a smart card through a user’s web browser using a Java applet. Throughout this article, you will discover how to build an applet that is based on the Sun PKCS#11 Provider, a cryptographic provider that is part of J2SE 5.0.
Signing documents requires the user to have a digital certificate along with a corresponding private key. Certificates are issued by certification authorities and can be stored in a file or on a smart card.
Smart cards are used to securely protect sensitive information such as private keys and certificates and provide better security because of the inability to copy the information stored in them. Private keys in the smart cards can be used to encrypt and sign data but cannot be extracted out of the card.
Background
In an earlier article (NakovDocumentSigner: A System for Digitally Signing Documents in Web Applications) a system was presented to digitally sign documents in a Web-based environment. The system was based on a Java applet that used a Web browser to send files. On the server side, there was a J2EE Web application that received the signed files and verified their digital signature and the certificate. The signing was performed with a local file that contains the client’s certificate and its private key, stored in the PKCS#12 file format (file with a PFX extension).
This article expands upon the Nakov Document Signer, found at NakovDocumentSigner, but this article will add some new functionality—signing documents in a Web environment with a smart card.
The Problem of Digital Signing in a Web-Based Environment with a Smart Card
Document signing with a smart card in a user’s Web browser (form fields, forms, files, and other) is an ongoing problem for which there is no standard solution, regardless of the platform and the Web browser type or version.
Today’s Web browsers do not have implemented functionality for the signing of attached files when they are sent from the client to the Web server. This leads to problems with the implementation of some custom applications in which the client needs to certify, in a trustworthy way, that he or she is the sender of a given document. Examples for such applications are found in e-government and e-banking systems, some financial systems, and others. Systems such as these obviously are going to contain sensitive data, and proper certification is a must.
There are two approaches to solve the problem.
- Use a proprietary extension for the Web browser (for example, CAPICOM in Internet Explorer and crypto.SignText() in Netscape and Mozilla).
- Use a Java applet, which would work nearly anywhere.
If you use extensions for the Web browser, signing parts of HTML forms through JavaScript can be easy, but the signing of a local file is still hard to do because JavaScript cannot read the local file system due to security restrictions. However, my Java applet can do this because it is signed.
By design, Java applets run with full permissions, without any security restrictions, and thus can read local files.
Building a Smart Card Applet
The goal here is to develop a technology that would digitally sign documents in a Web-based environment with a smart card. The developed technology should be independent from the operating system and from the user’s Web browser. A Java applet will be used to execute the signing in the user’s Web browser.
First, the ways and means for smart card access delivered by Java 5.0 will be presented. Afterwards, step-by-step development of the signing Java applet will be presented. It will sign files in the user’s Web browser using a smart card before sending them to the Web server by a standard HTML form.
Defining What Is Meant By “Smart Cards”
Before moving forward, we need to have a common understanding of smart cards technology. Smart cards are magnetic or chip-cards that preserve and protect sensitive information (private keys, certificates, and other) more safely than a file record.
Most of the modern smart cards have a cryptoprocessor and a protected data area that cannot be copied. In fact, only the cryptoprocessor, which encrypts and signs data, has access to the protected area of the smart card. Developers cannot extract private keys from the card, only use the services of its cryptoprocessor instead.
It is impossible to extract the protected information from the smart card, thus making it exceptionally reliable because access to the card’s private keys can happen only if the card is physically present. If the card is stolen, its owner can inform the issuing party to revoke the card.
When using the PKCS#12 certificate keystore, preserved in a file (with a PFX file extension), there is a risk of somebody copying the file and obtaining access to its password. The password can get stolen with a keylogger, trojan horse, spyware, or other hacking tool. Also, after that, one also can extract the private keys without the knowledge of the keystore owner. When using a smart card with a cryptoprocessor, it is not possible for the private keys to be extracted and copied due to hardware restrictions. The only possibility is if the card is physically stolen. However, the risk of this happening is far smaller in comparison to the risk of the certificate file being copied.
Smart Card Access Standards
As mentioned above, smart cards have their own cryptoprocessor, their own operating system, protected memory, and their own file system. The access to them is performed on different levels with different protocols. The standard, ISO 7816, specifies the main smart card components and describes the low-level access to them.
For higher-level access to smart cards, the PKCS#11 standard is used. This standard defines the application interface for interaction with the cryptographic devices (cryptographic tokens)—for instance, smart cards, hardware cryptographic accelerators, and others.
The software that is delivered along with the smart card usually contains an implementation of the PKCS#11 standard for the specific smart card and card reader. The implementation usually is a library (.dll file in Windows or .so file in Linux and UNIX) that can be loaded dynamically and can be used from all applications installed locally.
For example, if an ISO 7816-compatible Utimaco Safeware smart card is used, then the PKCS#11 implementation for this card is contained in the software package “Utimaco SafeGuard Smartcard Provider,” which is delivered along with the card. For this article, you can assume you complete an installation of this software under Microsoft Windows XP. In Windows XP, the library that implements PKCS #11 will be seen as the file C:WINDOWSsystem32pkcs201n.dll.
The PKCS#11 standard doesn’t allow physical extraction of the private keys from the smart card, but it is possible to use these keys to encrypt, decrypt, or sign data. Of course, for such an operation to be performed, the user must enter the PIN code beforehand; this protects the access to the smart card.
The PKCS#11 standard gives an interface for accessing the protected keys and certificate keystores, located on the smart card. For this reason, the smart cards can be operated in a way very similar to the operation with PKCS#12 keystores. A PKCS#11-compatible smart card, however, has much more capability than PKCS#12 keystores. Accessing protected keys and certificate keystores from a smart card using PKCS#11 is very similar to accessing information from PKCS#12 keystore files. Smart cards, however, have much more capability than PKCS#12 keystores; for example, encryption and signature built-in functionality.
Accessing Smart Cards from Java
From version 5.0 onwards of the Java 2 platform, each of these versions have built-in smart card access support. This smart access is performed via the PKCS#11 interface (Cryptographic Token Interface Standard); the interaction with it is established through the cryptographic services provider, “Sun PKCS#11 Provider“.
Using the Sun PKCS#11 Provider
In Java 5.0, to access the smart card “Sun PKCS#11 Provider,” a cryptographic services provider is used.
Unlike most Java Cryptography Architecture (JCA) providers, the Sun PKCS#11 Provider does not implement the cryptographic functionality directly; it relies on a native PKCS#11 implementation to which it forwards all operations. This implementation must be available as a .dll file in Windows or a .so file in UNIX and Linux. For example, if you use the Utimaco SafeGuard Smartcard Provider for Windows, the PKCS#11 implementation is the library pkcs201n.dll.
Configuring the Sun PKCS#11 Provider
It is important to remember that, to be used, the Sun PKCS#11 Provider must first be registered as a cryptographic services provider in JCA. The registration can be performed statically or dynamically. The following two sections will demonstrate how to both dynamically and statically register this provider.
Static Registration of the Sun PKCS#11 Provider
Static registration of the Sun PKCS#11 provider requires a change in the file:
%JAVA_HOME%/lib/security/java.security
To add a new security provider configuration, you need to insert the following lines:
# Configuration for security providers 1..6 omitted security.provider.7=sun.security.pkcs11.SunPKCS11 C:smartcardsconfigpkcs11.cfg
The pkcs11.cfg file, indicated in the preceding configuration, must contain the Sun PKCS#11 Provider settings. This file is in text format and describes some properties of the provider, such as the path to the PKCS#11 native library implementation.
Dynamic Registration of the Sun PKCS#11 Provider
For the dynamic registration of the “Sun PKCS#11 Provider,” you must instantiate the sun.security.pkcs11.SunPKCS11 class registered in JCA. The name of the configuration file containing its settings should be given as a parameter. The following is an example:
String pkcs11ConfigFile = "c:smartcardsconfigpkcs11.cfg"; Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(pkcs11ConfigFile); Security.addProvider(pkcs11Provider);
Configuration File of the Sun PKCS#11 Provider
In both cases, for the static and for the dynamic registration, a configuration file is required. This is necessary so that the SunPKCS11 class can read the path to the native library that implements the PKCS#11 standard. Here is an example of such a configuration file:
pkcs11.cfg
name = SmartCard library = c:windowssystem32pkcs201n.dll
The file contains two settings, as shown: name and library. The value of the name property is used as a part of the name for the instance of the PKCS#11 provider in JCA. The library property sets the path to the library that implements the PKCS#11 standard. If it is required to work with several smart cards simultaneously, the Sun PKCS#11 Provider must be registered several times with a different name each time.
In the configuration file, some additional properties can be set (described in the reference documentation), but the only mandatory properties are name and library.
Using the Sun PKCS#11 Provider Without a Configuration File
If you do not want to use an external configuration file, you can set the settings of the Sun PKCS#11 Provider dynamically by a stream. This can be done in the following way:
String pkcs11config = "name = SmartCardn" + "library = c:windowssystem32pkcs201n.dll"; byte[] pkcs11configBytes = pkcs11config.getBytes(); ByteArrayInputStream configStream = newByteArrayInputStream(pkcs11configBytes); Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(configStream); security.addProvider(pkcs11Provider);
In this example, you create a stream that reads the configuration settings, not from a text file but from a string. You first convert the string to a byte array and later create a stream from it. At last, you initialize the Sun PKCS#11 provider by passing this stream to it.
Unregistering the Sun PKCS#11 Provider
When you no longer need the PKCS#11 security provider, you should unregister it to deallocate the resources used. This can be done in the following way:
Provider pkcs11Provider = ...; String pkcs11ProviderName = pkcs11Provider.getName(); Security.removeProvider(pkcs11ProviderName);
Some PKCS#11 native implementations require the provider to be unregistered after you’ve finished signing. Otherwise, an active session with the smart card can remain open and can cause all further signing attempts to fail. Another possibility for unregistering the PKCS#11 security provider is the following:
Security.removeProvider("SunPKCS11-SmartCard");
The name of the provider is constructed by the prefix “SunPKCS11-,” followed by the name given in the configuration file (pkcs11.cfg).
Extracting a Keystore from a Smart Card
After you have registered and configured successfully the Sun PKCS#11 Provider, you can use it to access certificates and keys from the smart card. This is performed with the standard Java class for accessing protected certificate keystores:
java.security.KeyStore
The following code is an example of how you can establish secured access to a protected certificate keystore located on a smart card:
char [] pin = {'1', '2', '3', '4'}; KeyStore smartCardKeyStore = KeyStore.getInstance("PKCS11"); smartCardKeyStore.load(null, pin);
The source code shown above expects that the Sun PKCS#11 Provider has been successfully registered and configured. For the smart card’s keystore to be read, it is necessary to provide the correct PIN code to access the smart card.
Obtaining Certificates and Private Keys from a Smart Card
After you have established access to the smart card keystore, you can extract the keys and certificates from it, as from a normal keystore. All keys, certificates, and certification chains are stored under aliases in the keystore. The names can be extracted through an iterator.
Here is an example in which all certificates from a given keystore, together with the information for their private keys, are extracted and printed:
KeyStore keyStore = ...; Enumeration aliasesEnum = keyStore.aliases(); while (aliasesEnum.hasMoreElements()) { String alias = (String)aliasesEnum.nextElement(); System.out.println("Alias: " + alias); X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); System.out.println("Certificate: " + cert); PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null); System.out.println("Private key: " + privateKey); }
This example usually works for the common keystores, as well as for keystores placed on a smart card. A password is not required in the case of accessing private keys stored on a smart card. This is true because the PIN code is sent beforehand during the creation of the KeyStore object. That’s why in the example the null value is set as a password.
At first sight, it looks as though the private key can be extracted from a smart card, but in practice this is not the case. The smart cards do not allow the extraction of keys, but only indirect access to them for signing, verifying signatures, encrypting, or decrypting. In the example above, the private key is not extracted, but only an interface to access it is given.
Signing Data with a Smart Card
After the interface for a given private key is extracted from a smart card, it can be used for the signing of data, as any other private key. The real signing is performed by calculation of the hash value of the document to be signed and sending this hash value to the smart card so that it will be signed with its cryptoprocessor. If the operation is successful, the card returns a calculated signature as a result of the hash value’s signing. This way, the private key is preserved away from risk because it is kept in secret, hidden somewhere on the card. Here is an example of signing data with a given interface to a private key (extracted from a smart card):
private static byte[] signDocument(byte[] aDocument, PrivateKey aPrivateKey) throws GeneralSecurityException { Signature signatureAlgorithm = Signature.getInstance("SHA1withRSA"); signatureAlgorithm.initSign(aPrivateKey); signatureAlgorithm.update(aDocument); byte[] digitalSignature = signatureAlgorithm.sign(); return digitalSignature; }
Java Applet for Signing with a Smart Card
In the article NakovDocumentSigner: A System for Digitally Signing Documents in Web Applications, you learned how to develop an applet (DigitalSignerApplet) that signs documents with a certificate, extracted from a PKCS#12 keystore (PFX file). You now will modify this applet to make it sign documents with a smart card. As a result, you will create an applet called SmartCardSignerApplet that is very similar to the old one (DigitalSignerApplet).
System Requirements for Accessing Smart Cards with Java Applets
The Java applet for signing with a smart card requires you to have installed Java Plug-In, version 5.0 or newer, on the client’s computer. This is needed because the applet uses the Sun PKCS#11 Provider, which is included in Java from version 5.0 onwards.
Implementation of the Applet for Signing with a Smart Card
You will not see a big difference between signing in with a Smart Card than you would by using a “PFX” file. In fact, the only difference is in the way of instantiating the certificate keystore. When operating with PKCS#12, the keystore is loaded from a PFX file, and when operating with a smart card, the store is loaded from the card through the PKCS#11 interface. The other difference is that instead of an access password, a smart card is required to have the PIN code. Everything else is the same, from the loading of the certificate to the signing of the file.
How the signing applet is designed and how it works has already been covered in my earlier article, NakovDocumentSigner: A System for Digitally Signing Documents in Web Applications . In this article, the applet is signed to gain access to the file system of the local machine. It looks like a button that is placed in a given HTML form with which it interacts.
This applet’s source code can be found in the Listing 1 code. The class SmartCardSignerApplet implements its main
functionality. (Note: Listings are on the pages following this
article )
The main class of the applet uses another additional class, PKCS11LibraryFileAndPINCodeDialog, which prompts the user to provide a PKCS#11 implementation library and the PIN code for accessing the smart card as seen in the listing found in Listing 2.
The applet also uses the class found in this hyperlink, Base64Utils for BASE64 encoding and decoding. Note that this is the same as in the applet for signing with PFX file.
How the Applet for Signing with a Smart Card Works
The applet for signing documents with a smart card in the user’s Web browser works in the exact same way as the applet you built for signing with PFX files from my previous article, NakovDocumentSigner: A System for Digitally Signing Documents in Web Applications.
First of all, through the class netscape.javascript.JSObject the name of the file is extracted for signing from the HTML form and this file is loaded in the memory. This is possible because the applet is signed and has access to the local file system.
After that, a dialog window pops up and the user is requested to select a PKCS#11 library and provide the PIN code to access the smart card. When the user selects the library with the PKCS#11 implementation and types the PIN code, you extract the certificate from the smart card, together with its certification chain (if it is present) and its private key. Actually, the private key is not physically extracted (because this is not possible by design), but a Java interface to access it is retrieved for later use.
The certification chain is encrypted in ASN.1 DER format and is written in text format (with BASE64 encoding) in the corresponding text field of the HTML form. The class JSObject is used again to access the form in the Web browser.
Later, the file read in the memory is signed with the private key from the smart card and the digital signature received is transformed to text format with BASE64 encoding, and is written in a field of the HTML form.
If an error occurs in one of the described steps, the user is shown the respective error message. An error can occur in many situations—if the signing file cannot be read, if the PKCS#11 library implementation is invalid, if the PIN code is invalid, if there is a missing certificate or private key on the smart card, if the HTML form cannot be accessed, and so on.
The applet’s graphic user interface is based on the libraries package found at AWT and JFC/Swing, which come with the standard JDK 5.0.
The dialog window for choosing the PKCS#11 implementation and PIN code gives the option to select only between .dll and .so files (Windows and Linux libraries). The last-used PKCS#11 library, with the full path to it, is saved in the applet’s configuration file. This file is located in the user’s home directory so that it can be used the next time this dialog window is shown.
To access the smart card, the Sun PKCS#11 Provider is used, which is instanced with Java reflection and is configured dynamically. The sun.security.pkcs11.SunPKCS11 class is instanced with the “reflection” technology, so that it is not linked statically with the applet. If the class is statically linked to the applet and this class is missing (such as in Java Plug-In versions smaller than 5.0), the applet wouldn’t load at all. The advantage of instantiating the class with reflection is that the applet will load successfully and will throw an exception if the required class is missing. In this case, the user will receive an appropriate error message.
After the PKCS#11 provider is registered, the access to the smart card keystore is performed through the class found at java.security.KeyStore. The applet is expecting the smart card keystore to contain only one record (alias), in which it holds the user’s certificate (possibly together with its certification chain) and its corresponding private key.
The implementation of all used cryptographic algorithms is ensured by the default cryptographic services provider SunJSSE, which is a standard part from JDK 5.0.
To sign files, the digital signature algorithm “SHA1withRSA” is used, which is available in Java through the class found at java.security.Signature, and is supported by most smart cards.
Compiling and Signing the Applet
For the applet to work properly, it must be signed. This is necessary because the applet will need to have access to the file system to read the file for signing. You can use the following script to generate a self-signed certificate that you then can use to sign the applet:
generate-certificate.bat
del SmartCardSignerApplet.jks keytool -genkey -alias signFiles -keystore SmartCardSignerApplet.jks -keypass !secret -dname "CN=Your Company" -storepass !secret pause
As a result of the script execution, you will obtain a keystore file, SmartCardSignerApplet.jks, which contains a generated certificate and its corresponding private key saved under the name “signFiles” and accessible with the password “!secret“. The format of the file is JKS (Java Keystore). This file will used by default from keytool, a command-line program for managing keystores in Java 2 SDK, Standard Edition.
To compile the source code of the applet, create the JAR archive and to sign it use the following script:
build-script.bat
set JAVA5_HOME=C:Progra~1Javajdk1.5.0_04 del *.class %JAVA5_HOME%binjavac -classpath .; "%JAVA5_HOME%jrelibplugin.jar" *.java del *.jar %JAVA5_HOME%binjar -cvf SmartCardSignerApplet.jar *.class %JAVA5_HOME%binjarsigner -keystore SmartCardSignerApplet.jks -storepass !secret -keypass !secret SmartCardSignerApplet.jar signFiles pause
The specified sequence of commands deletes all compiled .class files, compiles all .java files, puts the obtained .class files in the archive SmartCardSignerApplet.jar, and signs this very same archive with the generated beforehand, self-signed certificate (located in the SmartCardSignerApplet.jks keystore file). For the compilation, JDK 5.0 or newer is required.
Testing the Applet with a Sample HTML Form
You can test the signed applet test with a sample HTML document that contains an HTML form that will interact with the applet (see Listing 4):
It is important to use the <object> and <embed> tags instead of the old <applet> tag. If you use the old tag, you cannot tell the Web browser that the required Java version is 5.0 or newer.
The sample HTML page contains an HTML form with three fields, an applet for signing, and a button for activation of the signing, which is actually located inside the applet. The three fields in the form are used to select the file to be sent, to save the certification chain used in the signing process, and to sign the received signature.
The Applet for Signing with a Smart Card in Action
When the user opens the sample HTML document, first it checks to see whether the machine has, found at Java Plug-In, 5.0 or newer installed. If the version is older, the user is automatically redirected to the Web site from which the new version can be downloaded and installed.If the machine has installed Java Plug-In 5.0, when the HTML document is loaded a dialog window pops up and requests the user confirmation to execute the applet with full permissions. See Figure 1.
Figure 1: Java Plug-In 5.0 requesting to execute the signed applet with full permissions
If the user trusts the company mentioned, and selects yes, the applet will start as usual. There is also a way to assign permanent trust in the company. In those cases, the Java Plug-In doesn’t request any more confirmation to execute it with full rights, but executes it directly in every consequent loading.
When the [Sign] button (which is located inside the applet) is pressed, the user is requested to provide a PKCS#11 library implementation and the PIN code for accessing the smart card. See Figure 2:
Figure 2: Signing with a smart card in a Web-based environment
If the signing is successful, the calculated signature and the certificate extracted from the smart card, are recorded into the HTML form, as seen in Figure 3:
Figure 3: An HTML form with a signed file in it
The applet for signing with a smart card can be tested online on the NakovDocumentSigner Web site.
The Subsystem for Signature and Certificate Verification
In the previous part of this series of articles, you saw how you can receive signed files, verify their signature, and the certificate used in the signing process. When using a smart card instead of a PFX file for the signing, there is no need to make any changes on the server side. This is why I am not going to discuss it again, but will only remind you that it is constructed as a J2EE Web application that accepts the sent file, the calculated digital signature and the user certificate, and verifies them. The certificate verification is done in two ways: directly or through verification of its certification chain (if it is available).
The NakovDocumentSigner System
The new and improved version of the NakovDocumentSigner, used for digitally signing documents in a Web environment and verification of digital signatures and certificates, consists of the following components:
- DigitalSignerApplet: A Java applet for signing
documents in the user’s Web browser using a certificate located in a PKCS#12
keystore (PFX file). - SmartCardSignerApplet: A Java applet for the
signing of documents in the user’s Web browser with a smart card. - DocumentSigningDemoWebApp: A Java and Struts-based Web application for receiving signed documents and verifying their digital signature and certificate. The application includes a subsystem for digital signature verification, a subsystem for direct verification of the certificate, and a subsystem for verification of the certification chain.
The system uses a traditional client-server architecture, implemented with a standard Web browser and a Java Web application, as seen in Figure 4:
Figure 4: Architecture of the system for signing documents in Web environment
On the client side, a standard Web browser is operating in which Java applets for document signings of documents are executed (DigitalSignerApplet and SmartCardSignerApplet).
On the server side a Java-based Web application is operating. It accepts the signed documents and checks their digital signature and the certificate used to sign them.
Download the NakovDocumentSigner System
The system NakovDocumentSigner, together with all its components, is distributed absolutely free and can be downloaded and used for any purpose without limitation, including as a part of commercial applications. The latest version of NakovDocumentSigner can be downloaded at here.
Summary
In this article, you learned how to implement a Java applet that signs files in the client’s Web browser with a smart card and sends them to the Web server for further processing. In the whole series of articles, starting with Digital Document Signing in Java-Based Web Applications, you learned how to implement a Java-based framework for signing documents in a Web environment with a PKCS#12 keystore file or with a smart card and how to verify the signatures, certificates, and certificate chains at the server side.
This technology can be easily extended in several ways:
- To provide signing of Web forms, not only attached
files. This can be done with a JavaScript that enumerates the form fields and
creates a single document from them for signing. - The later technology could be further extended to support the XMLDSIG standard that can sign and send to the server the Web forms along with their signatures as a signed XML.
About the Authors
Svetlin Nakovis a technical director of the National Academy for Software Development, where he trains software specialists for practical work in the IT industry. He has many years of professional experience as a software developer, consultant, and trainer. His interests include the Java technologies, the .NET platform, and information security. Svetlin is an author of several books and lots of scientific and technical publications in the area of software development.
Nikolay Nedyalkov is a president of the Association for Information Security (ISECA) and works to apply the world’s best practices to assure information security at a national level and when conducting business online. Nikolay is a professional software developer, consultant and lecturer with serious experience.
Listing 1: SmartCardSignerApplet.java
import java.applet.Applet; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ByteArrayInputStream; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.security.*; import java.security.cert.CertPath; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.lang.reflect.Constructor; import netscape.javascript.JSException; import netscape.javascript.JSObject; /** * Applet for digital signing documents with a smart card. The * applet is intended to be placed in a HTML document containing * a single HTML form that is used for applet input/output. * The applet accepts several parameters - the name of the field * in the HTML form that contains the file name to be signed * and the names of the fields in the HTML form, where the * certification chain and signature should be stored. * * If the signing process is successful, the signature and * certification chain fields in the HTML form are filled. * Otherwise, an error message explaining the failure reason * is shown. * * The applet asks the user to locate in his local file system * the PKCS#11 implementation library that is part of software * that come with the smart card and the smart card reader. * Usually this is a Windows .DLL file located in Windows * system32 directory or .so library * (e.g. C:windowssystem32pkcs201n.dll). * * The applet also asks the user to enter his PIN code for * accessing the smart card. If the smart card contains a * certificate and a corresponding private key, the signature of * the file is calculated and is placed in the HTML form. * In addition to the calculated signature the certificate with * its full certification chain is extracted from the smart card * and is placed in the HTML form too. The digital signature is * placed as Base64-encoded sequence of bytes. The certification * chain is placed as ASN.1 DER-encoded sequence of bytes, * additionally encoded in Base64. In case the smart card contains * only one certificate without its full certification chain, a * chain consisting of this single certificate is extracted and * stored in the HTML form instead of a full certification chain. * * Digital signature algorithm used is SHA1withRSA. The length of * the calculated signature depends on the length of the private * key on the smart card. * * The applet should be able to access the local machine's file * system for reading and writing. Reading the local file system * is required for the applet to access the file that should be * signed. Writing the local file system is required for the applet * to save its settings in the user's home directory. Accessing * the local file system is not possible by default, but if the * applet is digitally signed (with jarsigner), it runs with no * security restrictions and can do anything. This applet should * be signed in order to run. * * Java Plug-In version 1.5 or higher is required for accessing * the PKCS#11 smart card functionality, so the applet will not * run in any other Java runtime environment. * * This file is part of NakovDocumentSigner digital document * signing framework for Java-based Web applications: * http://www.nakov.com/documents-signing/ * * Copyright (c) 2005 by Svetlin Nakov - http://www.nakov.com * All rights reserved. This code is freeware. It can be used * for any purpose as long as this copyright statement is not * removed or modified. */ public class SmartCardSignerApplet extends Applet { private static final String FILE_NAME_FIELD_PARAM = "fileNameField"; private static final String CERT_CHAIN_FIELD_PARAM = "certificationChainField"; private static final String SIGNATURE_FIELD_PARAM = "signatureField"; private static final String SIGN_BUTTON_CAPTION_PARAM = "signButtonCaption"; private static final String PKCS11_KEYSTORE_TYPE = "PKCS11"; private static final String X509_CERTIFICATE_TYPE = "X.509"; private static final String CERTIFICATION_CHAIN_ENCODING = "PkiPath"; private static final String DIGITAL_SIGNATURE_ALGORITHM_NAME = "SHA1withRSA"; private static final String SUN_PKCS11_PROVIDER_CLASS = "sun.security.pkcs11.SunPKCS11"; private Button mSignButton; /** * Initializes the applet - creates and initializes its graphical * user interface. Actually the applet consists of a single * button, that fills its all surface. The button's caption is * taken from the applet parameter SIGN_BUTTON_CAPTION_PARAM. */ public void init() { String signButtonCaption = this.getParameter(SIGN_BUTTON_CAPTION_PARAM); mSignButton = new Button(signButtonCaption); mSignButton.setLocation(0, 0); Dimension appletSize = this.getSize(); mSignButton.setSize(appletSize); mSignButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { signSelectedFile(); } }); this.setLayout(null); this.add(mSignButton); } /** * Signs the selected file. The file name comes from a field * in the HTML document. The result consists of the calculated * digital signature and certification chain, both placed in * fields in the HTML document, encoded in Base64 format. * The HTML document should contain only one HTML form. * The name of the field, that contains the name of the file * to be signed is obtained from FILE_NAME_FIELD_PARAM applet * parameter. The names of the output fields for the signature * and the certification chain are obtained from the parameters * CERT_CHAIN_FIELD_PARAM and SIGNATURE_FIELD_PARAM. The user * is asked to choose a PKCS#11 implementation library and a * PIN code for accessing the smart card. */ private void signSelectedFile() { try { // Get the file name to be signed from the form in the // HTML document JSObject browserWindow = JSObject.getWindow(this); JSObject mainForm = (JSObject) browserWindow.eval("document.forms[0]"); String fileNameFieldName = this.getParameter(FILE_NAME_FIELD_PARAM); JSObject fileNameField = (JSObject) mainForm.getMember(fileNameFieldName); String fileName = (String) fileNameField.getMember("value"); // Perform the actual file signing CertificationChainAndSignatureBase64 signingResult = signFile(fileName); if null) { // Document signed. Fill the certificate and // signature fields String certChainFieldName = this.getParameter(CERT_CHAIN_FIELD_PARAM); JSObject certChainField = (JSObject) mainForm.getMember(certChainFieldName); certChainField.setMember("value", signingResult.mCertificationChain); String signatureFieldName = this.getParameter(SIGNATURE_FIELD_PARAM); JSObject signatureField = (JSObject) mainForm.getMember(signatureFieldName); signatureField.setMember("value", signingResult.mSignature); else { // User canceled signing } } catch (DocumentSignException dse) { // Document signing failed. Display error message String errorMessage = dse.getMessage(); JOptionPane.showMessageDialog(this, errorMessage); } catch (SecurityException se) { se.printStackTrace(); JOptionPane.showMessageDialog(this, "Unable to access the local file system.n" + "This applet should be started with full security permissions.n" + "Please accept to trust this applet when the Java Plug-In asks you."); } catch (JSException jse) { jse.printStackTrace(); JOptionPane.showMessageDialog(this, "Unable to access some of the fields of then" + "HTML form. Please check the applet parameters."); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, "Unexpected error: " + e.getMessage()); } } /** * Signs given local file. The certificate and private key to * be used for signing come from the locally attached smart * card. The user is requested to provide a PKCS#11 * implementation library and the PIN code for accessing the * smart card. @param aFileName the name of the file to be * signed. * @return the digital signature of the given file and the * certification chain of the certificatie used for signing the * file, both Base64-encoded or null if the signing process is * canceled by the user. * @throws DocumentSignException when a problem arised during * the singing process (e.g. smart card access problem, * invalid certificate, invalid PIN code, etc.) */ private CertificationChainAndSignatureBase64 signFile(String aFileName) throws DocumentSignException { // Load the file for signing byte[] documentToSign = null; try { documentToSign = readFileInByteArray(aFileName); } catch (IOException ioex) { String errorMsg = "Cannot read the file for signing " + aFileName + "."; throw new DocumentSignException(errorMsg, ioex); } // Show a dialog for choosing PKCS#11 implementation library // and smart card PIN PKCS11LibraryFileAndPINCodeDialog pkcs11Dialog = new PKCS11LibraryFileAndPINCodeDialog(); boolean dialogConfirmed; try { dialogConfirmed = pkcs11Dialog.run(); } finally { pkcs11Dialog.dispose(); } if (dialogConfirmed) { String oldButtonLabel = mSignButton.getLabel(); mSignButton.setLabel("Working..."); mSignButton.setEnabled(false); try { String pkcs11LibraryFileName = pkcs11Dialog.getLibraryFileName(); String pinCode = pkcs11Dialog.getSmartCardPINCode(); // Do the actual signing of the document with the // smart card CertificationChainAndSignatureBase64 signingResult = signDocument(documentToSign, pkcs11LibraryFileName, pinCode); return signingResult; finally { mSignButton.setLabel(oldButtonLabel); mSignButton.setEnabled(true); } } else { return null; } } private CertificationChainAndSignatureBase64 signDocument( byte[] aDocumentToSign, String aPkcs11LibraryFileName, String aPinCode) throws DocumentSignException { if (aPkcs11LibraryFileName.length() == 0) { String errorMessage = "It is mandatory to choose " + "a PCKS#11 native implementation library for " +
"smart card (.dll or .so file)!"; throw new DocumentSignException(errorMessage); } // Load the keystore from the smart card using the specified // PIN code KeyStore userKeyStore = null; try{ userKeyStore = loadKeyStoreFromSmartCard(
aPkcs11LibraryFileName, aPinCode); catch (Exception ex) { String errorMessage = "Cannot read the keystore from "
"the smart card.n" + "Possible reasons:n" + " - The smart card reader in not connected.n" + " - The smart card is not inserted.n" + " - The PKCS#11 implementation library is invalid.n" + " - The PIN for the smart card is incorrect.n" + "Problem details: " + ex.getMessage(); throw new DocumentSignException(errorMessage, ex); } // Get the private key and its certification chain from the // keystore PrivateKeyAndCertChain privateKeyAndCertChain = null; try { privateKeyAndCertChain = getPrivateKeyAndCertChain(userKeyStore); } catch (GeneralSecurityException gsex) { String errorMessage = "Cannot extract the private key " + "and certificate from the smart card. Reason: " + gsex.getMessage(); throw new DocumentSignException(errorMessage, gsex); } // Check if the private key is available PrivateKey privateKey = privateKeyAndCertChain.mPrivateKey; if (privateKey == null) { String errorMessage = "Cannot find the private key on " +
"the smart card."; throw new DocumentSignException(errorMessage); } // Check if X.509 certification chain is available Certificate[] certChain = privateKeyAndCertChain.mCertificationChain; if (certChain == null) { String errorMessage = "Cannot find the certificate on " +
"the smart card."; throw new DocumentSignException(errorMessage); } // Create the result object CertificationChainAndSignatureBase64 signingResult = new CertificationChainAndSignatureBase64(); // Save X.509 certification chain in the result encoded in // Base64 try { signingResult.mCertificationChain = encodeX509CertChainToBase64(certChain); } catch (CertificateException cee) { String errorMessage = "Invalid certificate on the "
"smart card."; throw new DocumentSignException(errorMessage); } // Calculate the digital signature of the file, // encode it in Base64 and save it in the result try { byte[] digitalSignature = signDocument(aDocumentToSign, privateKey); signingResult.mSignature = Base64Utils.base64Encode(digitalSignature); catch (GeneralSecurityException gsex) { String errorMessage = "File signing failed.n" + "Problem details: " + gsex.getMessage(); throw new DocumentSignException(errorMessage, gsex); } return signingResult; } /** * Loads the keystore from the smart card using its PKCS#11 * implementation library and the Sun PKCS#11 security provider. * The PIN code for accessing the smart card is required. */ private KeyStore loadKeyStoreFromSmartCard(String aPKCS11LibraryFileName, String aSmartCardPIN) throws GeneralSecurityException, IOException { // First configure the Sun PKCS#11 provider. It requires a // stream (or file) containing the configuration parameters - // "name" and "library". String pkcs11ConfigSettings = "name = SmartCardn" + "library = " + aPKCS11LibraryFileName; byte[] pkcs11ConfigBytes = pkcs11ConfigSettings.getBytes(); ByteArrayInputStream confStream = new ByteArrayInputStream(pkcs11ConfigBytes); // Instantiate the provider dynamically with Java reflection try { Class sunPkcs11Class = Class.forName(SUN_PKCS11_PROVIDER_CLASS); Constructor pkcs11Constr = sunPkcs11Class.getConstructor( java.io.InputStream.class); Provider pkcs11Provider = (Provider) pkcs11Constr.newInstance(confStream); Security.addProvider(pkcs11Provider); catch (Exception e) { throw new KeyStoreException("Can initialize " + "Sun PKCS#11 security provider. Reason: " +
e.getCause().getMessage()); } // Read the keystore form the smart card char[] pin = aSmartCardPIN.toCharArray(); KeyStore keyStore = KeyStore.getInstance(PKCS11_KEYSTORE_TYPE); keyStore.load(null, pin); return keyStore; } /** * @return private key and certification chain corresponding * to it, extracted from the given keystore. The keystore is * considered to have only one entry that contains both * certification chain and its corresponding private key. * If the keystore has no entries, an exception is thrown. */ private PrivateKeyAndCertChain getPrivateKeyAndCertChain( KeyStore aKeyStore) throws GeneralSecurityException { Enumeration aliasesEnum = aKeyStore.aliases(); if (aliasesEnum.hasMoreElements()) { String alias = (String)aliasesEnum.nextElement(); Certificate[] certificationChain = aKeyStore.getCertificateChain(alias); PrivateKey privateKey = (PrivateKey) aKeyStore.getKey(alias, null); PrivateKeyAndCertChain result = new PrivateKeyAndCertChain(); result.mPrivateKey = privateKey; result.mCertificationChain = certificationChain; return result; else { throw new KeyStoreException("The keystore is empty!"); } } /** * @return Base64-encoded ASN.1 DER representation of given * X.509 certification chain. */ private String encodeX509CertChainToBase64 (Certificate[] aCertificationChain) throws CertificateException { List certList = Arrays.asList(aCertificationChain); CertificateFactory certFactory = CertificateFactory.getInstance(X509_CERTIFICATE_TYPE); CertPath certPath = certFactory.generateCertPath(certList); byte[] certPathEncoded = certPath.getEncoded(CERTIFICATION_CHAIN_ENCODING); String base64encodedCertChain = Base64Utils.base64Encode(certPathEncoded); return base64encodedCertChain; } /** * Reads the specified file into a byte array. */ private byte[] readFileInByteArray(String aFileName) throws IOException { File file = new File(aFileName); FileInputStream fileStream = new FileInputStream(file); try { int fileSize = (int) file.length(); byte[] data = new byte[fileSize]; int bytesRead = 0; while (bytesRead < fileSize) { bytesRead += fileStream.read(
data, bytesRead, fileSize-bytesRead); } return data; } finally { fileStream.close(); } } /** * Signs given document with a given private key. */ private byte[] signDocument(byte[] aDocument, PrivateKey aPrivateKey) throws GeneralSecurityException { Signature signatureAlgorithm = Signature.getInstance(DIGITAL_SIGNATURE_ALGORITHM_NAME); signatureAlgorithm.initSign(aPrivateKey); signatureAlgorithm.update(aDocument); byte[] digitalSignature = signatureAlgorithm.sign(); return digitalSignature; } /** * Data structure that holds a pair of private key and * certification chain corresponding to this private key. */ static class PrivateKeyAndCertChain { public PrivateKey mPrivateKey; public Certificate[] mCertificationChain; } /** * Data structure that holds a pair of Base64-encoded * certification chain and digital signature. */ static class CertificationChainAndSignatureBase64 { public String mCertificationChain = null; public String mSignature = null; } /** * Exception class used for document signing errors. */ static class DocumentSignException extends Exception { public DocumentSignException(String aMessage) { super(aMessage); } public DocumentSignException(String aMessage, Throwable aCause) { super(aMessage, aCause); } } }
Listing 2: PKCS11LibraryFileAndPINCodeDialog.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.filechooser.FileFilter; import java.io.*; import java.util.Properties; /** * Dialog for choosing PKCS#11 implementation library file and * PIN code for accessing the smart card. Allows the user to * choose a PKCS#11 library file (.dll / .so) and enter a PIN code * for the smart card. The last used library file name is remembered * in the config file called ".smart_card_signer_applet.config" * located in the user's home directory in order to be * automatically shown the next time when the same user accesses * this dialog. * * This file is part of NakovDocumentSigner digital document * signing framework for Java-based Web applications: * http://www.nakov.com/documents-signing/ * * Copyright (c) 2005 by Svetlin Nakov - http://www.nakov.com * All rights reserved. This code is freeware. It can be used * for any purpose as long as this copyright statement is not * removed or modified. */ public class PKCS11LibraryFileAndPINCodeDialog extends JDialog { private static final String CONFIG_FILE_NAME = ".smart_card_signer_applet.config"; private static final String PKCS11_LIBRARY_FILE_NAME_KEY = "last-PKCS11-file-name"; private JButton mBrowseForLibraryFileButton = new JButton(); private JTextField mLibraryFileNameTextField = new JTextField(); private JLabel mChooseLibraryFileLabel = new JLabel(); private JTextField mPINCodeTextField = new JPasswordField(); private JLabel mEnterPINCodeLabel = new JLabel(); private JButton mSignButton = new JButton(); private JButton mCancelButton = new JButton(); private boolean mResult = false; /** * Initializes the dialog - creates and initializes its GUI * controls. */ public PKCS11LibraryFileAndPINCodeDialog() { // Initialize the dialog this.getContentPane().setLayout(null); this.setSize(new Dimension(426, 165)); this.setBackground(SystemColor.control); this.setTitle("Select PKCS#11 library file and smart " +
"card PIN code"); this.setResizable(false); // Center the dialog in the screen Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension dialogSize = this.getSize(); int centerPosX = (screenSize.width - dialogSize.width) / 2; int centerPosY = (screenSize.height - dialogSize.height) / 2; setLocation(centerPosX, centerPosY); // Initialize certificate keystore file label mChooseLibraryFileLabel.setText( "Please select your PKCS#11 implementation library file (.dll / .so) :"); mChooseLibraryFileLabel.setBounds(new Rectangle(10, 5, 400, 15)); mChooseLibraryFileLabel.setFont(new Font("Dialog", 0, 12)); // Initialize certificate keystore file name text field mLibraryFileNameTextField.setBounds(
new Rectangle(10, 25, 315, 20)); mLibraryFileNameTextField.setFont(
new Font("DialogInput", 0, 12)); mLibraryFileNameTextField.setEditable(false); mLibraryFileNameTextField.setBackground(SystemColor.control); // Initialize browse button mBrowseForLibraryFileButton.setText("Browse"); mBrowseForLibraryFileButton.setBounds(
new Rectangle(330, 25, 80, 20)); mBrowseForLibraryFileButton.addActionListener(
new ActionListener() { public void actionPerformed(ActionEvent e) { browseForLibraryButton_actionPerformed(); } }); // Initialize PIN code label mEnterPINCodeLabel.setText("Enter the PIN code to access " +
"your smart card:"); mEnterPINCodeLabel.setBounds(new Rectangle(10, 55, 350, 15)); mEnterPINCodeLabel.setFont(new Font("Dialog", 0, 12)); // Initialize PIN code text field mPINCodeTextField.setBounds(new Rectangle(10, 75, 400, 20)); mPINCodeTextField.setFont(new Font("DialogInput", 0, 12)); // Initialize sign button mSignButton.setText("Sign"); mSignButton.setBounds(new Rectangle(110, 105, 75, 25)) mSignButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { signButton_actionPerformed(); } }); // Initialize cancel button mCancelButton.setText("Cancel"); mCancelButton.setBounds(new Rectangle(220, 105, 75, 25)); mCancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cancelButton_actionPerformed(); } }); // Add the initialized components into the dialog's content pane this.getContentPane().add(mChooseLibraryFileLabel, null); this.getContentPane().add(mLibraryFileNameTextField, null); this.getContentPane().add(mBrowseForLibraryFileButton, null); this.getContentPane().add(mEnterPINCodeLabel, null); this.getContentPane().add(mPINCodeTextField, null); this.getContentPane().add(mSignButton, null); this.getContentPane().add(mCancelButton, null); this.getRootPane().setDefaultButton(mSignButton); // Add some functionality for focusing the most appropriate // control when the dialog is shown this.addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent windowEvent) { String libraryFileName = mLibraryFileNameTextField.getText(); if (libraryFileName != null && libraryFileName.length() != 0) mPINCodeTextField.requestFocus(); else mBrowseForLibraryFileButton.requestFocus(); } }); } /** * Called when the "Browse" button is pressed. * Shows file choose dialog and allows the user to locate a * library file. */ private void browseForLibraryButton_actionPerformed() { JFileChooser fileChooser = new JFileChooser(); LibraryFileFilter libraryFileFilter = new LibraryFileFilter(); fileChooser.addChoosableFileFilter(libraryFileFilter); String libraryFileName = mLibraryFileNameTextField.getText(); File directory = new File(libraryFileName).getParentFile(); fileChooser.setCurrentDirectory(directory); if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { String selectedLibFile = fileChooser.getSelectedFile().getAbsolutePath(); mLibraryFileNameTextField.setText(selectedLibFile); } } /** * Called when the sign button is pressed. Closses the dialog * and sets the result flag to true to indicate that the user * is confirmed the information entered in the dialog. */ private void signButton_actionPerformed() { mResult = true; this.setVisible(false); } /** * Called when the cancel button is pressed. Closes the dialog * and sets the result flag to false that indicates that the * dialog is canceled. */ private void cancelButton_actionPerformed() { mResult = false; this.setVisible(false); } /** * @return the file name with full path to it where the dialog * settings are stored. */ private String getConfigFileName() { String configFileName = System.getProperty("user.home") + System.getProperty("file.separator") + CONFIG_FILE_NAME; return configFileName; } /** * Loads the dialog settings from the dialog configuration file. * These settings consist of a single value - the last used * library file name with its full path. */ private void loadSettings() throws IOException { String configFileName = getConfigFileName(); FileInputStream configFileStream = new FileInputStream(configFileName); try { Properties configProps = new Properties(); configProps.load(configFileStream); // Apply setings from the config file String lastLibraryFileName = configProps.getProperty(PKCS11_LIBRARY_FILE_NAME_KEY); if (lastLibraryFileName != null) mLibraryFileNameTextField.setText(lastLibraryFileName); else mLibraryFileNameTextField.setText(""); } finally { configFileStream.close(); } } /** * Saves the dialog settings to the dialog configuration file. * These settings consist of a single value - the last used * library file name with its full path. */ private void saveSettings() throws IOException { // Create a list of settings to store in the config file Properties configProps = new Properties(); String currentLibraryFileName = mLibraryFileNameTextField.getText(); configProps.setProperty(PKCS11_LIBRARY_FILE_NAME_KEY, currentLibraryFileName); // Save the settings in the config file String configFileName = getConfigFileName(); FileOutputStream configFileStream = new FileOutputStream(configFileName); try { configProps.store(configFileStream, ""); } finally { configFileStream.close(); } } /** * @return the library file selected by the user. */ public String getLibraryFileName() { String libraryFileName = mLibraryFileNameTextField.getText(); return libraryFileName; } /** * @return the PIN code entered by the user. */ public String getSmartCardPINCode() { String pinCode = mPINCodeTextField.getText(); return pinCode; } /** * Shows the dialog and allow the user to choose library file * and enter a PIN code. * @return true if the user click sign button or false if the * user cancels the dialog. */ public boolean run() { try { loadSettings(); } catch (IOException ioex) { // Loading settings failed. Default settings will be used. } setModal(true); this.setVisible(true); try { if (mResult) { saveSettings(); } } catch (IOException ioex) { // Saving settings failed. Cannot handle this problem. } return mResult; } /** * File filter class, intended to accept only .dll and .so files. */ private static class LibraryFileFilter extends FileFilter { public boolean accept(File aFile) { if (aFile.isDirectory()) { return true; } String fileName = aFile.getName().toLowerCase(); boolean accepted = (fileName.endsWith(".dll") || fileName.endsWith(".so")); return accepted; } public String getDescription() { return "PKCS#11 v2.0 ot later implementation library " + "(.dll, .so)"; } } }
Listing 3: Base64Utils.java
/** * Provides utilities for Base64 encode/decode of binary data. */ public class Base64Utils { private static byte[] mBase64EncMap, mBase64DecMap; /** * Class initializer. Initializes the Base64 alphabet * (specified in RFC-2045). */ static { byte[] base64Map = { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' }; mBase64EncMap = base64Map; mBase64DecMap = new byte[128]; for (int i=0; i<mBase64EncMap.length; i++) mBase64DecMap[mBase64EncMap[i]] = (byte) i; } /** * This class isn't meant to be instantiated. */ private Base64Utils() { } /** * Encodes the given byte[] using the Base64-encoding, * as specified in RFC-2045 (Section 6.8). * * @param aData the data to be encoded * @return the Base64-encoded <var>aData</var> * @exception IllegalArgumentException if NULL or empty * array is passed */ public static String base64Encode(byte[] aData) { if ((aData == null) || (aData.length == 0)) throw new IllegalArgumentException(
"Cannot encode NULL or empty byte array."); byte encodedBuf[] = new byte[((aData.length+2)/3)*4]; // 3-byte to 4-byte conversion int srcIndex, destIndex; for (srcIndex=0, destIndex=0; srcIndex < aData.length-2; srcIndex += 3) { encodedBuf[destIndex++] =
mBase64EncMap[(aData[srcIndex] >>> 2) & 077]; encodedBuf[destIndex++] = mBase64EncMap[
(aData[srcIndex+1] >>> 4) & 017 | (aData[srcIndex] << 4) & 077]; encodedBuf[destIndex++] = mBase64EncMap[
(aData[srcIndex+2] >>> 6) & 003 | (aData[srcIndex+1] << 2) & 077]; encodedBuf[destIndex++] = mBase64EncMap[
aData[srcIndex+2] & 077]; } // Convert the last 1 or 2 bytes if (srcIndex < aData.length) { encodedBuf[destIndex++] =
mBase64EncMap[(aData[srcIndex] >>> 2) & 077]; if (srcIndex < aData.length-1) { encodedBuf[destIndex++] = mBase64EncMap[(aData[srcIndex+1] >>> 4) & 017 | (aData[srcIndex] << 4) & 077]; encodedBuf[destIndex++] =
mBase64EncMap[(aData[srcIndex+1] << 2) & 077]; } else { encodedBuf[destIndex++] =
mBase64EncMap[(aData[srcIndex] << 4) & 077]; } } // Add padding to the end of encoded data while (destIndex < encodedBuf.length) { encodedBuf[destIndex] = (byte) '='; destIndex++; } String result = new String(encodedBuf); return result; } /** * Decodes the given Base64-encoded data, * as specified in RFC-2045 (Section 6.8). * * @param aData the Base64-encoded aData. * @return the decoded <var>aData</var>. * @exception IllegalArgumentException if NULL or empty data * is passed */ public static byte[] base64Decode(String aData) { if ((aData == null) || (aData.length() == 0)) throw new IllegalArgumentException(
"Cannot decode NULL or empty string."); byte[] data = aData.getBytes(); // Skip padding from the end of encoded data int tail = data.length; while (data[tail-1] == '=') tail--; byte decodedBuf[] = new byte[tail - data.length/4]; // ASCII-printable to 0-63 conversion for (int i = 0; i < data.length; i++) data[i] = mBase64DecMap[data[i]]; // 4-byte to 3-byte conversion int srcIndex, destIndex; for (srcIndex = 0, destIndex=0; destIndex < decodedBuf.length-2; srcIndex += 4, destIndex += 3) { decodedBuf[destIndex] = (byte)
( ((data[srcIndex] << 2) & 255) | ((data[srcIndex+1] >>> 4) & 003) ); decodedBuf[destIndex+1] = (byte)
( ((data[srcIndex+1] << 4) & 255) | ((data[srcIndex+2] >>> 2) & 017) ); decodedBuf[destIndex+2] = (byte)
( ((data[srcIndex+2] << 6) & 255) | (data[srcIndex+3] & 077) ); } // Handle last 1 or 2 bytes if (destIndex < decodedBuf.length) decodedBuf[destIndex] = (byte) (
((data[srcIndex] << 2) & 255) | ((data[srcIndex+1] >>> 4) & 003) ); if (++destIndex < decodedBuf.length) decodedBuf[destIndex] = (byte)
( ((data[srcIndex+1] << 4) & 255) | ((data[srcIndex+2] >>> 2) & 017) ); return decodedBuf; } }
Listing 4:
TestSmartCardSignerApplet.html
<html>
<head>
<title>Test Smart Card Signer Applet</title>
</head>
<body>
<form name="mainForm" method="post" action="FileUploadServlet">
Choose file to upload and sign:
<input type="file" name="fileToBeSigned">
<br>
Certification chain:
<input type="text" name="certificationChain">
<br>
Signature:
<input type="text" name="signature">
</form>
<object
classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
codebase="http://java.sun.com/update/1.5.0/jinstall-1_5-windows-i586.cab#Version=5,0,0,5"
width="130" height="25" name="SmartCardSignerApplet">
<param name="code" value="SmartCardSignerApplet">
<param name="archive" value="SmartCardSignerApplet.jar">
<param name="mayscript" value="true">
<param name="type" value="application/x-java-applet;version=1.5">
<param name="scriptable" value="false">
<param name="fileNameField" value="fileToBeSigned">
<param name="certificationChainField" value="certificationChain">
<param name="signatureField" value="signature">
<param name="signButtonCaption" value="Sign selected file">
<comment>
<embed
type="application/x-java-applet;version=1.5"
code="SmartCardSignerApplet" archive="SmartCardSignerApplet.jar"
width="130" height="25" scriptable="true"
pluginspage="http://java.sun.com/products/plugin/index.html#download"
fileNameField="fileToBeSigned"
certificationChainField="certificationChain"
signatureField="signature"
signButtonCaption="Sign selected file">
</embed>
<noembed>
Smart card signing applet can not be started because
Java Plugin 1.5 or newer is not installed.
</noembed>
</comment>
</object>
</body>
</html>