public class PKCS11SignApplet
extends javax.swing.JApplet
implements java.awt.event.ActionListener
This is an implementation of a full-featured signing applet in a web
environment.
PKCS11SignApplet
sequence of operation follows:
!!! BE CAREFUL !!!
THE APPLET RELIES HEAVILY ON BINARY DIGEST RECEIVED FROM THE SERVER, AND DOES NOT COMPUTE ITSELF THE DIGEST.
THIS CAN EXPOSE THE CLIENT TO THE RISK OF SIGNING DATA DIFFERENT FROM WHAT HE INTENDED, IF THE SERVER IS COMPROMISED.
HENCE YOU SHOULD USE THE APPLET ONLY IN A VERY WELL CONTROLLED ENVIRONMENT, AND THE SERVER SHOULD BE AUTHENTICATED
IN A STRONG MANNER (USE OF SSL/TLS IS STRONGLY SUGGESTED).
FURTHERMORE, THE PROBLEM OF DISPLAYING THE DOCUMENT TO BE SIGNED (THE "TRUSTED VIEWER" PROBLEM) IS ENTIRELY LEFT
TO THE WEB APPLICATION; WE REMIND THAT A CORRECT PRESENTATION OF THE DOCUMENT IS MANDATORY FOR THE LEGAL VALUE
OF THE SIGNATURE, ACCORDING TO ITALIAN DIGITAL SIGNATURE LAW.
Trying to approach these issues, and for dealing with CMS enveloping, j4sign provides a server side companion for the applet,
CMSBuilder
.
It is designed for answering to applet requests, an in particular for streaming content to be signed
in a synchronized way with digest generation, checking correctness on the fly.
The native library requirements for dealing with the tokens (the JNI part,
such as the excellent pkcs11 wrapper developed by IAIK of Graz University of Technology,
and the pcsc wrapper taken from Open Card Framework project), along with the corresponding
native libraries, are encapsulated in a standard Java Extension, named
SmartCardAccess
. See it.trento.comune.j4sign.installer
and Creating and Using Extensions.
The extension is deployed automatically the first time the applet is loaded.
The ultimate dependency for the applet is the cryptoki library, which has to
be provided by the PKCS11 token manufacturer. The
PCSCHelper
class uses the pcsc wrapper
trying to infer the correct library from the ATR string returned from the
token.
Some words about security; all downloaded jars, including the
SmartCardAccess
extension, has to be signed in order to work;
this is needed for many reasons:
Note that recent java 7 JVMs have stricter policies about Applets running outside the sandbox,
and many jar manifest attributes are now mandatory.
The applet "may script" and is "scriptable", that is it exposes its public
methods to java script on the page it is embedded into, and can call java
script functions defined in that page.
Special-named java script functions are indeed used to:
viewDocument()
setDigest()
setDigestFirmato()
setCertificato()
eseguiSubmit()
<head>
<script language="JavaScript">
function eseguiSubmit(){
document.firmaform.submit();
}
function setDigest(aString){
document.firmaform.digestDomandaBASE64.value=aString;
}
function setDigestFirmato(aString){
document.firmaform.digestDomandaFirmatoBASE64.value=aString;
}
function setCertificato(aString){
document.firmaform.certificatoBASE64.value=aString;
}
//IE insists to cache requests, add viewcount parameter for changing url
//at each view request.
var viewcount=0;
function viewDocument(hash){
document.getElementById('doctosign').src="viewdocument?datahash="+hash+"&vc="+viewcount++;
}
</script>
</head>
<body>
<div align=center>
<iframe id="doctosign" src="about:blank" width="100%" height="500">
<p>Your browser does not support iframes</p>
</iframe>
<strong>Tutti i dati nei campi della form sono codificati BASE64</strong>
<form action="." name="firmaform" method="POST" >
<strong>Digest</strong><br>
<input type="test" name="digestDomandaBASE64" value="" size="68" /><br>
<strong>Digest crittato</strong><br>
<textarea name="digestDomandaFirmatoBASE64" rows="5" cols="64" ></textarea><br>
<strong>Certificato</strong><br>
<textarea name="certificatoBASE64" rows="5" cols="64" ></textarea><br>
</form>
<applet height="150" width="600" code="dummy">
<param name="jnlp_href" value="https://my.web.site//my-application/j4sign-pkcs11-applet.jnlp">
<param name="singleSignature" value="<%=(session.getAttribute("doclist") == null)%>">
<param name="digestPath" value="digest">
<param name="encryptedDigestPath" value="encrypteddigest">
<param name="datahash" value="${pi.contentHash}">
</applet>
</div>
Some parameters are to be generated dinamically, in this case (taken form a JSP) via Java and JSTL expressions.
In the above example, the applet is deployed via JNLP, using an xml descriptor (the j4sign-pkcs11-applet.jnlp) that provides all
the standard parameters in the deployment scenario. In particular, you can provide a central location
for the applet, avoiding to embed it in all applications.
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="" href="">
<information>
<title>j4sign signature Applet</title>
<vendor>My firm name</vendor>
</information>
<resources>
<!-- Application Resources -->
<j2se version="1.7+"/>
<jar href="https://my.web.site/signature/SignatureApplet-signed.jar" main="true" />
</resources>
<applet-desc
name="Signer"
main-class="it.trento.comune.j4sign.examples.PKCS11SignApplet"
width="600"
height="150">
<param name="mayscript" value="true" />
<param name="scriptable" value="true" />
</applet-desc>
<security>
<all-permissions/>
</security>
</jnlp>
jnlp_href
: URI of the jnlp descriptor.singleSignature
: boolean, whether this applet is going to sign one document or iterate over many ones.digestPath
: path for POSTing certificate and receiving data to sign.encryptedDigestPath
: path for POSTing raw signature.datahash
: hash of the content to be signed. It is displayed by the applet, and checked server side when applet triggers document streaming.mayscript
: if true, the applet may call javascript functions on the embedding page.scriptable
: if true, the applet may have public method called by javascript functions on the embedding page.submitAfterSigning
: if true, automatically submits the form after a successful
signature, calling the eseguiSubmit()
javascript function.true
debug
adds a scroll panel for viewing debug informations; set applet height to more than 200 to see the content.false
cryptokilib
name of the cryptoki library to load. It has to be inside PATH. This bypasses detection mechanism via ATR.SimpleSignApplet
,
Serialized Formjavax.swing.JApplet.AccessibleJApplet
java.applet.Applet.AccessibleApplet
Modifier and Type | Field and Description |
---|---|
private javax.swing.JTextArea |
certArea |
private byte[] |
certificate |
private FindCertTask |
certTask |
private java.lang.String |
cryptokiLib |
(package private) boolean |
debug |
private static java.lang.String |
DIGEST_MD5 |
private static java.lang.String |
DIGEST_SHA1 |
private static java.lang.String |
DIGEST_SHA256 |
private java.lang.String |
digestAlg |
private java.lang.String |
digestPath |
private DigestSignTask |
dsTask |
private java.lang.String |
encAlg |
private java.lang.String |
encodedContentHash |
private java.lang.String |
encodedDigest |
private byte[] |
encryptedDigest |
private java.lang.String |
encryptedDigestPath |
private static java.lang.String |
ENCRYPTION_RSA |
private javax.swing.Timer |
findTimer |
private javax.swing.JTextField |
hashField |
private short |
iteration |
private java.io.PrintStream |
log |
private javax.swing.JTextArea |
logArea |
private boolean |
makeDigestOnToken |
static int |
ONE_SECOND |
private javax.swing.JProgressBar |
progressBar |
private javax.swing.JPasswordField |
pwd |
private javax.swing.JButton |
s |
private javax.swing.JButton |
sd |
private javax.swing.JTextField |
signingTimeGMT |
(package private) boolean |
singleSignature |
(package private) boolean |
submitAfterSigning |
private javax.swing.Timer |
timer |
static java.lang.String |
VERSION |
accessibleContext, rootPane, rootPaneCheckingEnabled
Constructor and Description |
---|
PKCS11SignApplet() |
Modifier and Type | Method and Description |
---|---|
void |
actionPerformed(java.awt.event.ActionEvent e)
GUI event management
The most important source of events is the pwd field, that triggers the creation of the signing task. |
private long |
algToMechanism(boolean digestOnToken,
java.lang.String digestAlg,
java.lang.String encryptionAlg) |
java.lang.String |
decode(java.lang.String s)
Base64 String to String decoding function. |
byte[] |
decodeToBytes(java.lang.String s)
Base64 String to byte[] decoding function. |
void |
destroy()
Cleans up whatever resources are being held.
|
private boolean |
detectCardAndCriptoki()
This triggers the PCSC wrapper stuff; a
PCSCHelper class is used
to detect reader and token presence, trying also to provide a candidate
PKCS#11 cryptoki for it; detection is bypassed if an applet parameter
forcing cryptoki selection is provided. |
private void |
enableControls(boolean enable)
Enables GUI controls (depending on debug mode).
|
java.lang.String |
encode(java.lang.String s)
Base64 String to String encoding function. |
java.lang.String |
encodeFromBytes(byte[] bytes)
Base64 byte[] to String encoding
function. |
private void |
findCert()
Starts the background task that scans the token looking for a suitable
certificate.
|
(package private) java.lang.String |
formatAsHexString(byte[] bytes)
returns an hex dump of the supplied
byte[] |
java.lang.String |
getAppletInfo()
Returns information about this applet.
|
byte[] |
getCertificate()
Gets the
x509 certificate as a byte[] . |
private java.lang.String |
getCryptokiLib()
Gets name of the cryptoki library in use
|
java.lang.String |
getDigestPath() |
java.lang.String |
getEncodedContentHash() |
java.lang.String |
getEncodedDigest()
Gets the digest
Base64 encoded. |
byte[] |
getEncryptedDigest()
Gets the raw encryptedDigest-
|
java.lang.String |
getEncryptedDigestPath() |
java.security.cert.X509Certificate |
getJavaCertificate() |
private java.lang.String |
httpPOST(java.net.URL url,
java.lang.String data) |
void |
init()
Initializes the applet.
|
private void |
initStatus(int min,
int max)
Initializes minimum and maximum status values for progress bar
|
private boolean |
isSingleSignature()
Is the form inside the embedding page to be submitted after signing?
|
private boolean |
retriveEncodedDigestFromServer(boolean reloadDocument) |
void |
returnCertificateToForm()
Interfaces via javascript with the embedding page for setting the
certificate field on the form.
|
void |
returnEncodedDigestToForm()
Interfaces via javascript with the embedding page for setting the digest
field on the form (useful only for testing purpose in debug mode).
|
void |
returnEncryptedDigestToForm()
Interfaces via javascript with the embedding page for setting the
encrypted digest field on the form.
|
private boolean |
returnEncryptedDigestToServer() |
(package private) void |
setAboutToSignStatus() |
private void |
setCertificate(byte[] newCertificate)
Setter method
|
private void |
setCryptokiLib(java.lang.String newCryptokiLib)
Setter method
|
void |
setDigestPath(java.lang.String digestPath) |
void |
setEncodedContentHash(java.lang.String encodedContentHash) |
void |
setEncodedDigest(java.lang.String data)
Setter method
|
void |
setEncryptedDigest(byte[] newEncryptedDigest)
Setter method
|
void |
setEncryptedDigestPath(java.lang.String encryptedDigestPath) |
void |
setSingleSignature(boolean single) |
(package private) void |
setStatus(int code,
java.lang.String statusString) |
(package private) void |
setStatus(int code,
java.lang.String statusString,
java.lang.String alertMessage)
Updates progress bar value and displays error alerts
|
void |
sign()
Initializes and starts the sign task.
|
void |
start()
Called to start the applet.
|
void |
stop()
Called to stop the applet.
|
private void |
submitForm()
Calls the javascript submit function on the embedding page.
|
private void |
viewDocument()
Calls the javascript "viewDocument" function on the embedding page.
|
addImpl, createRootPane, getAccessibleContext, getContentPane, getGlassPane, getGraphics, getJMenuBar, getLayeredPane, getRootPane, getTransferHandler, isRootPaneCheckingEnabled, paramString, remove, repaint, setContentPane, setGlassPane, setJMenuBar, setLayeredPane, setLayout, setRootPane, setRootPaneCheckingEnabled, setTransferHandler, update
getAppletContext, getAudioClip, getAudioClip, getCodeBase, getDocumentBase, getImage, getImage, getLocale, getParameter, getParameterInfo, isActive, isValidateRoot, newAudioClip, play, play, resize, resize, setStub, showStatus
add, add, add, add, add, addContainerListener, addPropertyChangeListener, addPropertyChangeListener, applyComponentOrientation, areFocusTraversalKeysSet, countComponents, deliverEvent, doLayout, findComponentAt, findComponentAt, getAlignmentX, getAlignmentY, getComponent, getComponentAt, getComponentAt, getComponentCount, getComponents, getComponentZOrder, getContainerListeners, getFocusTraversalKeys, getFocusTraversalPolicy, getInsets, getLayout, getListeners, getMaximumSize, getMinimumSize, getMousePosition, getPreferredSize, insets, invalidate, isAncestorOf, isFocusCycleRoot, isFocusCycleRoot, isFocusTraversalPolicyProvider, isFocusTraversalPolicySet, layout, list, list, locate, minimumSize, paint, paintComponents, preferredSize, print, printComponents, processContainerEvent, processEvent, remove, removeAll, removeContainerListener, removeNotify, setComponentZOrder, setFocusCycleRoot, setFocusTraversalKeys, setFocusTraversalPolicy, setFocusTraversalPolicyProvider, setFont, transferFocusDownCycle, validate, validateTree
action, add, addComponentListener, addFocusListener, addHierarchyBoundsListener, addHierarchyListener, addInputMethodListener, addKeyListener, addMouseListener, addMouseMotionListener, addMouseWheelListener, bounds, checkImage, checkImage, coalesceEvents, contains, contains, createImage, createImage, createVolatileImage, createVolatileImage, disable, disableEvents, dispatchEvent, enable, enable, enableEvents, enableInputMethods, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, firePropertyChange, getBackground, getBaseline, getBaselineResizeBehavior, getBounds, getBounds, getColorModel, getComponentListeners, getComponentOrientation, getCursor, getDropTarget, getFocusCycleRootAncestor, getFocusListeners, getFocusTraversalKeysEnabled, getFont, getFontMetrics, getForeground, getGraphicsConfiguration, getHeight, getHierarchyBoundsListeners, getHierarchyListeners, getIgnoreRepaint, getInputContext, getInputMethodListeners, getInputMethodRequests, getKeyListeners, getLocation, getLocation, getLocationOnScreen, getMouseListeners, getMouseMotionListeners, getMousePosition, getMouseWheelListeners, getName, getParent, getPeer, getPropertyChangeListeners, getPropertyChangeListeners, getSize, getSize, getToolkit, getTreeLock, getWidth, getX, getY, gotFocus, handleEvent, hasFocus, hide, imageUpdate, inside, isBackgroundSet, isCursorSet, isDisplayable, isDoubleBuffered, isEnabled, isFocusable, isFocusOwner, isFocusTraversable, isFontSet, isForegroundSet, isLightweight, isMaximumSizeSet, isMinimumSizeSet, isOpaque, isPreferredSizeSet, isShowing, isValid, isVisible, keyDown, keyUp, list, list, list, location, lostFocus, mouseDown, mouseDrag, mouseEnter, mouseExit, mouseMove, mouseUp, move, nextFocus, paintAll, postEvent, prepareImage, prepareImage, printAll, processComponentEvent, processFocusEvent, processHierarchyBoundsEvent, processHierarchyEvent, processInputMethodEvent, processKeyEvent, processMouseEvent, processMouseMotionEvent, processMouseWheelEvent, remove, removeComponentListener, removeFocusListener, removeHierarchyBoundsListener, removeHierarchyListener, removeInputMethodListener, removeKeyListener, removeMouseListener, removeMouseMotionListener, removeMouseWheelListener, removePropertyChangeListener, removePropertyChangeListener, repaint, repaint, repaint, requestFocus, requestFocus, requestFocusInWindow, requestFocusInWindow, reshape, revalidate, setBackground, setBounds, setBounds, setComponentOrientation, setCursor, setDropTarget, setEnabled, setFocusable, setFocusTraversalKeysEnabled, setForeground, setIgnoreRepaint, setLocale, setLocation, setLocation, setMaximumSize, setMinimumSize, setName, setPreferredSize, setSize, setSize, setVisible, show, show, size, toString, transferFocus, transferFocusBackward, transferFocusUpCycle
private javax.swing.JPasswordField pwd
private javax.swing.JTextArea certArea
private javax.swing.JButton sd
private javax.swing.JButton s
private javax.swing.JTextArea logArea
private DigestSignTask dsTask
private FindCertTask certTask
private javax.swing.Timer findTimer
private javax.swing.JTextField signingTimeGMT
private javax.swing.JTextField hashField
private javax.swing.Timer timer
private javax.swing.JProgressBar progressBar
boolean debug
boolean singleSignature
boolean submitAfterSigning
private java.lang.String encodedDigest
private byte[] encryptedDigest
private java.io.PrintStream log
public static final int ONE_SECOND
public static final java.lang.String VERSION
private java.lang.String cryptokiLib
private java.lang.String digestPath
private java.lang.String encryptedDigestPath
private byte[] certificate
private boolean makeDigestOnToken
private static final java.lang.String DIGEST_MD5
private static final java.lang.String DIGEST_SHA1
private static final java.lang.String DIGEST_SHA256
private static final java.lang.String ENCRYPTION_RSA
private java.lang.String digestAlg
private java.lang.String encAlg
private java.lang.String encodedContentHash
private short iteration
public java.lang.String getEncodedContentHash()
public void setEncodedContentHash(java.lang.String encodedContentHash)
public void init()
cryptokilib
applet parameter.public java.lang.String getDigestPath()
public void setDigestPath(java.lang.String digestPath)
public java.lang.String getEncryptedDigestPath()
public void setEncryptedDigestPath(java.lang.String encryptedDigestPath)
public void actionPerformed(java.awt.event.ActionEvent e)
DigestSignTask
carried in a separate thread, avoiding to lock the gui; a
Timer
is used to refresh a progress bar every second,
querying the task status.actionPerformed
in interface java.awt.event.ActionListener
e
- The event to deal with.ActionListener.actionPerformed(java.awt.event.ActionEvent)
void setAboutToSignStatus()
public java.lang.String decode(java.lang.String s)
Base64
String to String decoding function. Relies on
decodeToBytes(String)
method.s
- The Base64
string to decode.public byte[] decodeToBytes(java.lang.String s)
Base64
String to byte[] decoding function. Warning: this
method relies on sun.misc.BASE64Decoder
s
- The Base64
string to decode.byte[]
public void destroy()
private void enableControls(boolean enable)
enable
- if true
, controls will be enabled.public java.lang.String encode(java.lang.String s)
Base64
String to String encoding function. Relies on
#encodeToBytes(String)
method.s
- The string to encode; UTF-8 charset is assumed.Base64
encoded string.public java.lang.String encodeFromBytes(byte[] bytes)
Base64
byte[]
to String
encoding
function. Warning: this method relies on
sun.misc.BASE64Encoder
The
- byte[]
to encode.Base64
encoded string.public java.lang.String getAppletInfo()
getAppletInfo
in class java.applet.Applet
public byte[] getCertificate()
x509
certificate as a byte[]
.x509
certificate as a byte[]
.private java.lang.String getCryptokiLib()
String
specifiyng the cryptoki library name.public java.lang.String getEncodedDigest()
Base64
encoded.Base64
encoding of the digest.public byte[] getEncryptedDigest()
byte[]
.private boolean isSingleSignature()
true
if the applet has to submit the form.public void setSingleSignature(boolean single)
private void initStatus(int min, int max)
min
- max
- public void returnCertificateToForm()
public void returnEncryptedDigestToForm()
public void returnEncodedDigestToForm()
private void setCertificate(byte[] newCertificate)
newCertificate
- private void setCryptokiLib(java.lang.String newCryptokiLib)
newCryptokiLib
- public void setEncodedDigest(java.lang.String data)
data
- public void setEncryptedDigest(byte[] newEncryptedDigest)
newEncryptedDigest
- void setStatus(int code, java.lang.String statusString, java.lang.String alertMessage)
code
- The status valuestatusString
- The status descriptionvoid setStatus(int code, java.lang.String statusString)
public void sign()
public void start()
public void stop()
private void submitForm()
private void viewDocument()
java.lang.String formatAsHexString(byte[] bytes)
byte[]
bytes
- the data to showprivate boolean detectCardAndCriptoki() throws java.io.IOException
PCSCHelper
class is used
to detect reader and token presence, trying also to provide a candidate
PKCS#11 cryptoki for it; detection is bypassed if an applet parameter
forcing cryptoki selection is provided.java.io.IOException
private long algToMechanism(boolean digestOnToken, java.lang.String digestAlg, java.lang.String encryptionAlg)
public java.security.cert.X509Certificate getJavaCertificate() throws java.security.cert.CertificateException
java.security.cert.CertificateException
private void findCert()
private boolean retriveEncodedDigestFromServer(boolean reloadDocument)
private boolean returnEncryptedDigestToServer()
private java.lang.String httpPOST(java.net.URL url, java.lang.String data) throws java.io.IOException
java.io.IOException