Java за цифрово подписване на документи в уеб


Глава 3.NakovDocumentSigner – система за подписване на документи в уеб среда



страница10/14
Дата14.08.2018
Размер1.94 Mb.
1   ...   6   7   8   9   10   11   12   13   14

Глава 3.NakovDocumentSigner – система за подписване на документи в уеб среда


До момента разгледахме основните проблеми, които стоят пред цифровото подписване на документи в уеб приложения и предложихме конкретни идеи за тяхното решаване чрез използване на подписан Java аплет. Анали­зирахме и проблемите по проверката на цифрови подписи, сертификати и сертифика­ционни вериги и направихме преглед на средствата на Java платформата за тяхното решаване. Нека сега разгледаме конкретната имплемен­тация на системата за подписване на документи в уеб среда.

3.1.Рамкова система NakovDocumentSigner


NakovDocumentSigner предста­влява рамкова система (framework) за циф­рово подписване на документи в Java-базирани уеб приложения [Nakov, 2004]. Системата е разработена в Софийски университет “Св. Климент Охридски” от Светлин Наков
. Тя се състои от следните компоненти:

  • Подписан Java аплет, който служи за цифрово подписване на файлове преди изпращането им. Поддържат се два варианта на аплета – DigitalSignerApplet за подписване със PKCS#12 хранилище (PFX файл) и SmartCardSignerApplet – за подписване със смарт карта.

  • Примерно уеб приложение DocumentSigningDemoWebApp, което посре­ща подписаните файлове и проверява дали полученият цифров подпис отговаря на получения файл и сертификат.

  • Проста подсистема за верификация на сертификати и сертифика­ционни вериги, реализирана като част от примерното уеб приложение. Поддържа директна верификация на сертификат и верификация на сертификационна верига.

Пълният сорс-код на системата NakovDocumentSigner, заедно с инструкции за компилиране, инсталиране и употреба, е достъпен от нейния сайт – http://www.nakov.com/documents-signing/.

3.2.Java аплет за подписване с PKCS#12 хранилище


Да разгледаме имплементацията на Java аплетът DigitalSignerApplet, който подписва документи в клиентския уеб браузър.

Системни изисквания за Java аплета за подписване в уеб среда


Java аплетът за подписване с PKCS#12 хранилище изисква инсталиран Java Plug-In
версия 1.4 или по-нова на машината на клиента. Това е необходимо, защото аплетът използва Java Cryptography Architecture
, която не е достъпна при по-ниските версии на Java Plug-In.

Аплетът не работи със стандартната виртуална машина, която идва с някои версии на Internet Explorer. Той е подписан, за работи с пълни права и да може да осъществява достъп до локалната файлова система на потре­бителя и работи нормално само ако потребителят му позволи да бъде изпълнен с пълни права.


Имплементация Java аплета


Аплетът следва твърдо описаната преди малко поредица от стъпки за подпис­ване на документи в клиентския уеб браузър и представлява сам по себе си един бутон, който се поставя в HTML формата за изпращане на файлове. Като параметри му се подават името на полето, от което се взима файлът за подписване и имената на полетата, в които се записват изчис­лената сигнатура и изпол­званият цифров сертификат, заедно с цялата му серти­фикационна верига.

Сорс кодът на аплета DigitalSignerApplet


Сорс кодът на аплета, който подписва документи в уеб браузъра на клиента със сертификат от PKCS#12 хранилище, се състои от няколко файла, достъпни от сайта на NakovDocumentSigner
. Ще ги разгледаме един по един. Да започнем с основния клас на аплета – DigitalSignerApplet:

DigitalSignerApplet.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.util.Arrays;

import java.util.Enumeration;

import java.util.List;

import java.security.GeneralSecurityException;

import java.security.KeyStoreException;

import java.security.KeyStore;

import java.security.PrivateKey;

import java.security.Signature;

import java.security.cert.CertPath;

import java.security.cert.Certificate;

import java.security.cert.CertificateException;

import java.security.cert.CertificateFactory;

import netscape.javascript.JSException;

import netscape.javascript.JSObject;

/**

* Applet for digital signing documents. 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 sucecssfull, the signature and certification chain

* fields in the HTML form are filled. Otherwise an error message explaining the

* failure reason is shown to the user.

*

* The applet asks the user to locate in his local file system a PFX file (PKCS#12

* keystore), that holds his certificate (with the corresponding certification chain)

* and its private key. Also the applet asks the user to enter his password for

* accessing the keystore and the private key. If the specified file contains a

* certificate and a corresponding private key that is accessible with supplied

* password, the signature of the file is calculated and is placed in the HTML form.

*

* The applet considers taht the password for the keystore and the password for the

* private key in it are the same (this is typical for the PFX files).

*

* In addition to the calculated signature the certification chain is extracted from

* the PFX file and is placed in the HTML form too. The digital signature is stored

* as Base64-encoded sequence of characters. The certification chain is stored as

* ASN.1 DER-encoded sequence of bytes, additionally encoded in Base64.

*

* In case the PFX file 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 the full certification chain.

*

* Digital singature algorithm used is SHA1withRSA. The length of the private key and

* respectively the length of the calculated singature depend on the length of the

* private key in the PFX file.

*

* 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 and the PFX keystore file. 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. This

* applet should be signed in order to run.

*

* A JRE version 1.4 or hihger is required for accessing the cryptography

* 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) 2003 by Svetlin Nakov - http://www.nakov.com

* National Academy for Software Development - http://academy.devbg.org

* 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 DigitalSignerApplet 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 PKCS12_KEYSTORE_TYPE = "PKCS12";

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 Button mSignButton;

/**

* Initializes the applet - creates and initializes its graphical user interface.

* Actually the applet consists of a single button, that fills its surface. The

* button's caption comes 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 applet extracts the certificate, its chain and its private key from

* a PFX file. The user is asked to select this PFX file and the password

* for accessing it.

*/

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 file signing

CertificationChainAndSignatureInBase64 signingResult=signFile(fileName);



if (signingResult != 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.mCertChain);

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 ask you.");

}

catch (JSException jse) {

jse.printStackTrace();

JOptionPane.showMessageDialog(this,



"Unable to access some of the fields in the\n" +

"HTML form. Please check applet parameters.");

}

catch (Exception e) {

e.printStackTrace();

JOptionPane.showMessageDialog(this, "Unexpected error: "+e.getMessage());

}

}

/**



* Signs given local file. The certification chain and private key to be used for

* signing are specified by the local user who choose a PFX file and password for

* accessing it.

* @param aFileName the name of the file to be signed.

* @return the digital signature of the given file and the certification chain of

* the certificate used for signing the file, both Base64-encoded or null if the

* signing process is canceled by the user.

* @throws DocumentSignException when a problem arise during the singing process

* (e.g. invalid file format, invalid certificate, invalid password, etc.)

*/

private CertificationChainAndSignatureInBase64 signFile(String aFileName)

throws DocumentSignException {

// Load the file for signing

byte[] documentToSign = null;

try {

documentToSign = readFileInByteArray(aFileName);

} catch (IOException ioex) {

String errorMsg = "Can not read the file for signing " + aFileName + ".";



throw new DocumentSignException(errorMsg, ioex);

}

// Show a dialog for selecting PFX file and password

CertificateFileAndPasswordDialog certFileAndPasswdDlg =

new CertificateFileAndPasswordDialog();

if (certFileAndPasswdDlg.run()) {

// Load the keystore from specified file using the specified password

String keyStoreFileName = certFileAndPasswdDlg.getCertificateFileName();



if (keyStoreFileName.length() == 0) {

String errorMessage = "It is mandatory to select a certificate " +



"keystore (.PFX or .P12 file)!";

throw new DocumentSignException(errorMessage);

}

String password = certFileAndPasswdDlg.getCertificatePassword();



KeyStore userKeyStore = null;

try {

userKeyStore = loadKeyStoreFromPFXFile(keyStoreFileName, password);

} catch (Exception ex) {

String errorMessage = "Can not read certificate keystore file (" +

keyStoreFileName + ").\nThe file is either not in PKCS#12 format"

+ " (.P12 or .PFX) or is corrupted or the password is invalid.";



throw new DocumentSignException(errorMessage, ex);

}

// Get the private key and its certification chain from the keystore

PrivateKeyAndCertChain privateKeyAndCertChain = null;

try {

privateKeyAndCertChain =

getPrivateKeyAndCertChain(userKeyStore, password);

} catch (GeneralSecurityException gsex) {

String errorMessage = "Can not extract certification chain and " +

"corresponding private key from the specified keystore file " +

"with given password. Probably the password is incorrect.";

throw new DocumentSignException(errorMessage, gsex);

}

// Check if a private key is available in the keystore

PrivateKey privateKey = privateKeyAndCertChain.mPrivateKey;

if (privateKey == null) {

String errorMessage = "Can not find the private key in the " +



"specified file " + keyStoreFileName + ".";

throw new DocumentSignException(errorMessage);

}

// Check if X.509 certification chain is available

Certificate[] certChain =

privateKeyAndCertChain.mCertificationChain;



if (certChain == null) {

String errorMessage = "Can not find neither certificate nor " +



"certification chain in the file " + keyStoreFileName + ".";

throw new DocumentSignException(errorMessage);

}

// Create the result object

CertificationChainAndSignatureInBase64 signingResult =

new CertificationChainAndSignatureInBase64();

// Save X.509 certification chain in the result encoded in Base64

try {

signingResult.mCertChain = encodeX509CertChainToBase64(certChain);

}

catch (CertificateException cee) {

String errorMessage = "Invalid certification chain found in the " +



"file " + keyStoreFileName + ".";

throw new DocumentSignException(errorMessage);

}

// Calculate the digital signature of the file,



// encode it in Base64 and save it in the result

try {

byte[] signature = signDocument(documentToSign, privateKey);

signingResult.mSignature = Base64Utils.base64Encode(signature);

} catch (GeneralSecurityException gsex) {

String errorMessage = "Error signing file " + aFileName + ".";



throw new DocumentSignException(errorMessage, gsex);

}

// Document signing completed succesfully



return signingResult;

}

else {



// Document signing canceled by the user

return null;

}

}



/**

* Loads a keystore from .PFX or .P12 file (file format should be PKCS#12)

* using given keystore password.

*/

private KeyStore loadKeyStoreFromPFXFile(String aFileName, String aKeyStorePass)

throws GeneralSecurityException, IOException {

KeyStore keyStore = KeyStore.getInstance(PKCS12_KEYSTORE_TYPE);

FileInputStream keyStoreStream = new FileInputStream(aFileName);

char[] password = aKeyStorePass.toCharArray();

keyStore.load(keyStoreStream, password);



return keyStore;

}

/**



* @return private key and certification chain corresponding to it, extracted

* from given keystore using given password to access the keystore and the same

* password to access the private key in it. The keystore is considered to have

* only one entry that contains both certification chain and the corresponding

* private key.

* If the certificate has no entries, an exception is trown. It the keystore has

* several entries, the first is used.

*/

private PrivateKeyAndCertChain getPrivateKeyAndCertChain(

KeyStore aKeyStore, String aKeyPassword)



throws GeneralSecurityException {

char[] password = aKeyPassword.toCharArray();

Enumeration aliasesEnum = aKeyStore.aliases();



if (aliasesEnum.hasMoreElements()) {

String alias = (String)aliasesEnum.nextElement();

Certificate[] certificationChain = aKeyStore.getCertificateChain(alias);

PrivateKey privateKey = (PrivateKey) aKeyStore.getKey(alias, password);

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 CertificationChainAndSignatureInBase64 {

public String mCertChain = 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);

}

}



}

Основният клас използва класа CertificateFileAndPasswordDialog за да предостави на потребителя възможност за избор на PFX файл и парола за достъп до него. Ето неговият сорс код:

CertificateFileAndPasswordDialog.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 certificate file name and password for it. Allows the user

* to choose a PFX file and enter a password for accessing it. The last used PFX

* file is remembered in the config file called ".digital_signer_applet.config",

* located in the user's home directory in order to be automatically shown the

* next time when the same user access 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) 2003 by Svetlin Nakov - http://www.nakov.com

* National Academy for Software Development - http://academy.devbg.org

* 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 CertificateFileAndPasswordDialog extends JDialog {

private static final String CONFIG_FILE_NAME = ".digital_signer_applet.config";

private static final String PFX_FILE_NAME_KEY = "last-PFX-file-name";

private JButton mBrowseForCertButton = new JButton();

private JTextField mCertFileNameTextField = new JTextField();

private JLabel mChooseCertFileLabel = new JLabel();

private JTextField mPasswordTextField = new JPasswordField();

private JLabel mEnterPasswordLabel = 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 CertificateFileAndPasswordDialog() {

// Initialize the dialog

this.getContentPane().setLayout(null);

this.setSize(new Dimension(426, 165));

this.setBackground(SystemColor.control);

this.setTitle("Select digital certificate");

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

mChooseCertFileLabel.setText(



"Please select your certificate keystore file (.PFX / .P12) :");

mChooseCertFileLabel.setBounds(new Rectangle(10, 5, 350, 15));

mChooseCertFileLabel.setFont(new Font("Dialog", 0, 12));

// Initialize certificate keystore file name text field

mCertFileNameTextField.setBounds(new Rectangle(10, 25, 315, 20));

mCertFileNameTextField.setFont(new Font("DialogInput", 0, 12));

mCertFileNameTextField.setEditable(false);

mCertFileNameTextField.setBackground(SystemColor.control);

// Initialize browse button

mBrowseForCertButton.setText("Browse");

mBrowseForCertButton.setBounds(new Rectangle(330, 25, 80, 20));

mBrowseForCertButton.addActionListener(new ActionListener() {



public void actionPerformed(ActionEvent e) {

browseForCertButton_actionPerformed();

}

});


// Initialize password label

mEnterPasswordLabel.setText("Enter the password for your private key:");

mEnterPasswordLabel.setBounds(new Rectangle(10, 55, 350, 15));

mEnterPasswordLabel.setFont(new Font("Dialog", 0, 12));



// Initialize password text field

mPasswordTextField.setBounds(new Rectangle(10, 75, 400, 20));

mPasswordTextField.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(mChooseCertFileLabel, null);

this.getContentPane().add(mCertFileNameTextField, null);

this.getContentPane().add(mBrowseForCertButton, null);

this.getContentPane().add(mEnterPasswordLabel, null);

this.getContentPane().add(mPasswordTextField, 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 certFileName = mCertFileNameTextField.getText();



if (certFileName != null && certFileName.length() != 0)

mPasswordTextField.requestFocus();



else

mBrowseForCertButton.requestFocus();

}

});


}

/**

* Called when the browse button is pressed.

* Shows file choose dialog and allows the user to locate a PFX file.

*/

private void browseForCertButton_actionPerformed() {

JFileChooser fileChooser = new JFileChooser();

PFXFileFilter pfxFileFilter = new PFXFileFilter();

fileChooser.addChoosableFileFilter(pfxFileFilter);

String certFileName = mCertFileNameTextField.getText();

File directory = new File(certFileName).getParentFile();

fileChooser.setCurrentDirectory(directory);

if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {

String selectedCertFile =

fileChooser.getSelectedFile().getAbsolutePath();

mCertFileNameTextField.setText(selectedCertFile);

}

}

/**



* 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;

hide();

}

/**



* Called when the cancel button is pressed. Closses the dialog and sets the

* result flag to false that indicates that the dialog is canceled.

*/

private void cancelButton_actionPerformed() {

mResult = false;

hide();

}

/**



* @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 PFX file name with its full path.

*/

private void loadSettings()

throws IOException {

// Load settings file

String configFileName = getConfigFileName();

FileInputStream configFileStream = new FileInputStream(configFileName);

Properties configProps = new Properties();

configProps.load(configFileStream);

configFileStream.close();



// Apply setings from the config file

String lastCertificateFileName =

configProps.getProperty(PFX_FILE_NAME_KEY);

if (lastCertificateFileName != null)

mCertFileNameTextField.setText(lastCertificateFileName);



else

mCertFileNameTextField.setText("");

}

/**

* Saves the dialog settings to the dialog configuration file. These settings

* consist of a single value - the last used PFX 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 currentCertificateFileName = mCertFileNameTextField.getText();

configProps.setProperty(PFX_FILE_NAME_KEY, currentCertificateFileName);



// Save the settings in the config file

String configFileName = getConfigFileName();

FileOutputStream configFileStream = new FileOutputStream(configFileName);

configProps.store(configFileStream, "");

configFileStream.close();

}

/**



* @return the PFX file selected by the user.

*/

public String getCertificateFileName() {

String certFileName = mCertFileNameTextField.getText();



return certFileName;

}

/**



* @return the password entered by the user.

*/

public String getCertificatePassword() {

String password = mPasswordTextField.getText();



return password;

}

/**



* Shows the dialog and allows the user to choose a PFX file and enter a

* password.

* @return true if the user click sign button or false if the user cancel the

* dialog.

*/

public boolean run() {

try {

loadSettings();

} catch (IOException ioex) {

// Loading settings failed. Can not handle this. Do nothing

}

setModal(true);



show();

try {

if (mResult)

saveSettings();

} catch (IOException ioex) {

// Saving settings failed. Can not handle this. Do nothing.

}

return mResult;

}

/**

* File filter class, intended to accept only .PFX and .P12 files.

*/

private static class PFXFileFilter extends FileFilter {

public boolean accept(File aFile) {

if (aFile.isDirectory()) {

return true;

}

String fileName = aFile.getName().toUpperCase();



boolean accepted =

(fileName.endsWith(".PFX") || fileName.endsWith(".P12"));



return accepted;

}

public String getDescription() {



return "PKCS#12 certificate keystore file (.PFX, .P12)";

}

}



}

Понеже в Java не предлага стандартно поддръжка на BASE64 кодиране, трябва да дефинираме собствена реализация. Ето нейният сорс код:

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

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 aData

* @exception IllegalArgumentException if NULL or empty array is passed

*/

public static String base64Encode(byte[] aData) {

if ((aData == null) || (aData.length == 0))

throw new IllegalArgumentException(

"Can not 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 aData.

* @exception IllegalArgumentException if NULL or empty data is passed

*/

public static byte[] base64Decode(String aData) {

if ((aData == null) || (aData.length() == 0))

throw new IllegalArgumentException("Can not 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;

}


}

Как работи аплетът за подписване на файл?


Аплетът използва класа netscape.javascript.JSObject
за достъп до поле­тата на HTML формата, взима от нея избрания от потребителя файл и го подписва. При подписването първо се прочита съдържанието на файла, след което се показва на потребителя диалогът за избор на PFX файл и парола за достъп до него.

След като потребителят избере PFX файл, този файл се прочита и от него се извлича личният ключ и съответната му сертификационна верига. Тази верига винаги започва със сертификата на потребителя, но е възможно да се състои единствено от него, т. е. да не съдържа други сертификати.

Ако извличането на личния ключ и сертификационната верига от PFX файла е успешно, сертифика­ционната верига се кодира по подходящ начин в текстов вид, за да може да бъде пренесена през текстово поле на HTML формата. Използва се стандартното кодиране PkiPath, което пред­ставлява последовател­ност от ASN.1 DER-кодирани сертификати. Получе­ната кодира­на сертификационна верига се кодира допълнително с Base64 за да добие текстов вид.

Следва извършване на самото подписване на документа с личния ключ, прочетен от PFX файла. Получената цифрова сигнатура се кодира в текстов вид с Base64 кодиране. Накрая текстовите стойности на извлечената от PFX файла сертификационна верига и получената сигнатура се записват в определени полета на HTML формата. Имената на тези полета, както и името на полето, съдържащо името на файла за подписване, се взимат от параметри, подадени на аплета.

За простота се очаква HTML документът, в който е разположен аплетът за подписване да съдържа точно една HTML форма.

Ако възникване грешка на някоя от описаните стъпки, на потребителя се показва подходящо съобщение за грешка. Грешка може да възникне при много ситуации – при невъзможност да бъде прочетен файлът за подпис­ване, при невъзможност да бъде прочетен PFX файлът, при невалиден формат на PFX файла, при липса на личен ключ, при липса на сертификат, при невалиден формат на сертификата, при невалидна парола за достъп до PFX файла, поради грешка при самото подпис­ване на прочетения файл, поради невъзможност за достъп до файловата система, поради невъзмож­ност за достъп до някое поле от формата, което е необходимо, и във всички други необичайни ситуации.

Диалогът за избор на PFX файл и парола дава възможност за избор само измежду PFX и P12 файлове. Последният използван PFX файл се запомня заедно с пълния път до него в конфигурационен файл, намиращ се личната директорията на потребителя (user home directory), за да бъде използван при следващо показване на същия диалог.

Графичният потребителски интерфейс на аплета е базиран на библиоте­ките AWT и JFC/Swing, които се доставят стандартно с JDK 1.4.

Реализацията на всички използвани криптографски алгоритми, се осигу­рява от доставчика на криптографски услуги по подразбиране SunJSSE, който също е част от JDK 1.4.

За подписването на файлове се използва алгоритъма за цифрови подписи SHA1withRSA. Това означава, че за изчисляване на хеш-стойността на документа се използва алгоритъм SHA1, а за шифрирането на тази хеш-стойност и получаване на цифровия подпис се използва алгоритъм RSA. Алгоритъмът SHA1withRSA е достъпен от Java Cryptography Architecture (JCA) посредством класа java.security.Signature.

Дължината на ключо­вете, използвани от RSA алгоритъма зависи от подадения от потребителя PFX файл и обикновено е 512 или 1024 бита. В зависимост от дължината на RSA ключа се получава цифрова сигнатура със същата дължина, обикновено 512 или 1024 бита (64 или 128 байта). След кодиране с Base64 тази сигнатура се записва съответно с 88 или 172 текстови символа.

Дължината на сертификационната верига зависи от броя сертификати, които я съставят и от съдържанието на всеки от тези сертификати. Обикно­вено след кодиране с Base64 тази верига има дължина от 200-300 до 8000-10000 текстови символа.

За осъществяването на достъп до защитеното хранилище за ключове и сертификати (PFX файла, който потребителят избира) се използва класът java.security.KeyStore. Аплетът очаква хранилището да бъде във формат PKCS#12 и да съдържа само един запис (alias), в който са записани лични­ят ключ на потребителя и сертификационната верига на неговия сертифи­кат. В частност тази сертификационна верига може да се състои и само от един сертификат. Аплетът очаква още паролата за достъп до хранилището да съвпада са паролата за достъп до личния ключ в него.

Компилиране и подписване на аплета


За да работи правилно аплетът, е необходимо той да бъде подписан. За подписването му може да се използва сертификат, издаден от някой серти­фикационен орган (PFX файл) или саморъчно-подписан сертификат. Можем да използваме следния скрипт за да си генерираме саморъчно-подписан сертификат:

generate-certificate.bat

del DigitalSignerApplet.jks

keytool -genkey -alias signFiles -keystore DigitalSignerApplet.jks -keypass !secret -dname "CN=Your Company" -storepass !secret

pause


Резултатът е файлът DigitalSignerApplet.jks, който представлява храни­лище за ключове и сертифи­кати, съдържащо генерирания сертификат и съответния му личен ключ, записани под име “signFiles”, достъпни с парола “!secret”. Форматът на изходния файл не е PKCS#12, а JKS (Java KeyStore), който се използва по подразбиране от програмката keytool.

За компилирането на сорс-кода на аплета, получаването на JAR архив и подписването на този архив можем да използваме следния скрипт:



build-script.bat

del *.class

javac -classpath .;"%JAVA_HOME%\jre\lib\plugin.jar" *.java


del *.jar

jar -cvf DigitalSignerApplet.jar *.class


jarsigner -keystore DigitalSignerApplet.jks -storepass !secret -keypass !secret DigitalSignerApplet.jar signFiles
pause

Посочената последователност от команди изтрива всички компилирани .class файлове, компилира всички .java файлове, които съставят аплета, пакетира получените .class файлове в JAR архив и подписва този архив с генерирания преди това саморъчно-подписан сертификат (намиращ се в хранилището DigitalSignerApplet.jks).

За компилирането на сорс-файловете на аплета е необходим JDK 1.4 или по-нова версия. Очаква се променливата на средата JAVA_HOME да има стойност, която отговаря на пълния път до директорията, в която е инсталиран JDK, а също в текущият път да е присъства bin директорията на JDK инсталацията.


Тестване на аплета с примерна HTML форма


Подписаният аплет можем да тестваме с примерен HTML документ, който съдържа подходяща HTML форма:

TestSignApplet.html

<html>

<head>

<title>Test Document Signer Applettitle>

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/products/plugin/autodl/jinstall-1_4-windows-i586.cab#Version=1,4,0,0"

width="130" height="25" mayscript="true">

<param name="type" value="application/x-java-applet;version=1.4">

<param name="code" value="DigitalSignerApplet">

<param name="archive" value="DigitalSignerApplet.jar">

<param name="mayscript" value="true">

<param name="scriptable" value="true">

<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.4"

pluginspage="http://java.sun.com/products/plugin/index.html#download"

code="DigitalSignerApplet" archive="DigitalSignerApplet.jar"

width="130" height="25"

mayscript="true" scriptable="true"

fileNameField="fileToBeSigned"

certificationChainField="certificationChain"

signatureField="signature"

signButtonCaption="Sign selected file">

<noembed>

Document signing applet can not be started because

Java Plugin 1.4 is not installed.

noembed>

embed>

comment>

object>





archive="DigitalSignerApplet.jar"

code="DigitalSignerApplet"

width="130" height="25" mayscript

fileNameField="fileToBeSigned"

certificationChainField="certificationChain"

signatureField="signature"

signButtonCaption="Sign selected file">



------------------------------------------------------------------------------->

body>

html>


Важно е да не използваме остарелия таг , защото при него няма начин да укажем на уеб браузъра, че аплетът може да работи само с JDK версия 1.4 или по-висока. Ако използваме тага, нашият аплет ще стартира на всяко JDK, но няма винаги да работи правилно.

Аплетът за подписване в уеб среда в действие


При отваряне на тестовият HTML документ от примера по-горе първо се проверява дали на машината има инсталиран Java Plug-In
1.4 или по-висока версия. Ако съответната версия на Java Plug-In не бъде намерена, потре­бителят се препраща автоматично към сайта, от който тя може да бъде изтеглена.

Ако на машината е инсталиран Java Plug-In 1.4 и уеб браузърът на потребителя е правилно конфигуриран да го използва за изпълнение на Java аплети, при зареждане на тестовия HTML документ се появява диалог, който пита дали да се позволи на подписания аплет, намиращ се в зареде­ната HTML страница, да бъде изпълнен с пълни права. Ако потреби­телят се съгласи, аплетът стартира нормално.

Целта на тестовата HTML страница от примера по-горе е да демонстрира подписването на файлове на машината на клиента. Примерът представлява HTML форма, съдържаща три полета, аплет за подписване и бутон за активиране на подписването, който всъщност се намира вътре в аплета. Трите полета във формата служат съответно за избор на файл за изпра­щане, за записване на сертифика­цион­ната верига, използвана при подпис­ването и за записване на получената сигнатура.

При натискане на бутона за подписване на потребителя се дава възмож­ност да избере PFX файл със сертификат и личен ключ и парола за достъп до него (фигура 4-1):





Фигура 4 18. Подписване в уеб среда – диалог за избор на PFX файл и парола

След това аплетът подписва избрания от HTML формата файл с личния ключ от избрания PFX файл. В резултат от подписването изчислената сигнатура и сертификационната верига се записват в съответното поле от HTML формата. След успешно подписване на избрания файл HTML формата изглежда по следния начин (фигура 4-2):





Фигура 4 19. HTML форма с подписан файл в нея

Полетата за сертификационна верига и сигнатура са попълнени от аплета със стойности, кодирани текстово във формат Base64.


Сертификати за тестови цели


За да използва аплета, се очаква потребителят да притежава цифров сертификат и съответен на него личен ключ, записани в PFX файл (PKCS#12 хранилище), като паролата за достъп до този файл трябва да съвпада с паролата за достъп до личния ключ към сертификата. Такъв PFX файл може да се придобие при закупу­ването на сертификат от някой сертифи­кационен орган или да се генерира с инструмента keytool.

За тестови цели могат да бъдат използвани демонстрационни цифрови сертификати, които могат да бъдат получени от уеб сайтовете на някои сертификационни органи като Thawte, VeriSign и GlobalSign. Тези сертифи­кационни органи издават сертификатите си през Интернет и в резултат потребителите ги получават инсталирани директно в техните уеб браузъри. За да бъдат използвани такива сертификати за подписване на документи с DigitalSignerApplet, те първо трябва да бъдат експортирани заедно с личния им ключ в .PFX или .P12 файл.



Каталог: books -> signatures
books -> В обятията на шамбала
books -> Книга се посвещава с благодарност на децата ми. Майка ми и жена ми ме научиха да бъда мъж
books -> Николай Слатински “Надеждата като лабиринт” София, Издателство “виденов & син”, 1993 год
books -> София, Издателство “Българска книжница”, 2004 год. Рецензенти доц д. ик н. Димитър Йончев, проф д-р Нина Дюлгерова Научен редактор проф д-р Петър Иванов
books -> Николай Слатински “Измерения на сигурността” София, Издателство “Парадигма”, 2000 год
books -> Книга 2 щастие и успех предисловие
books -> Превръщане на числа от една бройна система в друга
books -> Тантриското преобразяване
signatures -> Java за цифрово подписване на документи в уеб


Поделитесь с Вашими друзьями:
1   ...   6   7   8   9   10   11   12   13   14


База данных защищена авторским правом ©obuch.info 2019
отнасят до администрацията

    Начална страница