Уеб формата за подписване със смарт карта (SignedFileUploadForm-SmartCard.jsp) е почти напълно еднаква с уеб формата за подписване с PKCS#12 хранилище. Единствената разлика е в името на използвания аплет и във версията на JDK, която е указана за аплета:
SignedFileUploadForm-SmartCard.jsp
|
<%
/**
* A JSP that contains the form for signing and uploading a file. The form
* contains 3 fields - the file to be uploaded, the certification chain and
* the digital signature. It also contains the SmartCardSignerApplet that
* signs the selected file on the client's machine.
*
* 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.
*/
%>
<%@ taglib uri="/WEB-INF/taglibs/struts-html.tld" prefix="html" %>
<html>
<body>
<html:form type="demo.SignedFileUploadActionForm" action="/SignedFileUpload"
method="post" enctype="multipart/form-data">
Please choose file to sign and upload: <html:file property="uploadFile"/> <br>
Certification chain (Base64-encoded): <html:text property="certChain"/> <br>
Digital signature (Base64-encoded): <html:text property="signature"/> <br>
<br>
<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="type" value="application/x-java-applet;version=1.5">
<param name="code" value="SmartCardSignerApplet">
<param name="archive" value="SmartCardSignerApplet.jar">
<param name="mayscript" value="true">
<param name="scriptable" value="true">
<param name="fileNameField" value="uploadFile">
<param name="certificationChainField" value="certChain">
<param name="signatureField" value="signature">
<param name="signButtonCaption" value="Sign selected file">
<comment>
<embed
type="application/x-java-applet;version=1.5"
pluginspage="http://java.sun.com/products/plugin/index.html#download"
code="SmartCardSignerApplet"
archive="SmartCardSignerApplet.jar"
width="130"
height="25"
mayscript="true"
scriptable="true"
scriptable="true"
fileNameField="uploadFile"
certificationChainField="certChain"
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>
<br>
<br>
<html:submit property="submit" value="Upload file"/>
html:form>
body>
html>
|
В страницата е указано, че трябва да има инсталиран Java Plug-In версия 1.5 или по-нова. Ако това не е така, браузърът на потребителя автоматично се препраща към страницата, от която може да се изтегли последната версия на Java Plug-In.
Клас за съхранение на данните от двете уеб форми
Зад Struts-формата за изпращане на подписан файл стои съответен Struts action form клас, който се използва за съхранение на данните от нея:
SignedFileUploadActionForm.java
|
package demo;
import org.apache.struts.action.ActionForm;
import org.apache.struts.upload.FormFile;
/**
* Struts action form class that maps to the form for uploading signed files
* (SignedFileUploadForm-PFX.jsp or SignedFileUploadForm-SmartCard.jsp). It is
* actually a data structure that consist of the uploaded file, the sender's
* certification chain and the digital signature of the uploaded file.
*
* 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 SignedFileUploadActionForm extends ActionForm {
private FormFile mUploadFile;
private String mCertChain;
private String mSignature;
public FormFile getUploadFile() {
return mUploadFile;
}
public void setUploadFile(FormFile aUploadFile) {
mUploadFile = aUploadFile;
}
public String getCertChain() {
return mCertChain;
}
public void setCertChain(String aCertChain) {
mCertChain = aCertChain;
}
public String getSignature() {
return mSignature;
}
public void setSignature(String aSignature) {
mSignature = aSignature;
}
}
|
Този клас не представлява нищо повече от обикновен Java bean, в който са дефинирани свойства, точно съответстващи на полетата от HTML формата. Когато потребителят попълни полетата на формата и я изпрати, Struts framework автоматично създава обект от този клас и прехвърля получените от формата данни в свойствата на този обект.
Действието, което посреща изпратената форма
Получените данни, записани в action form обекта се обработват от действието /SignedFileUpload, което съответства на Struts action класа SignedFileUploadAction:
SignedFileUploadAction.java
|
package demo;
import javax.servlet.http.*;
import org.apache.struts.action.*;
/**
* Struts action class for handling the results of submitting the forms
* SignedFileUploadForm-PFX.jsp and SignedFileUploadForm-SmartCard.jsp.
*
* It gets the data from the form as SignedFileUploadActionForm object and puts
* this object in the current user's session with key "signedFileUploadActionForm".
* After that this action redirects the user's Web browser to
* ShowSignedFileUploadResults.jsp that is used to display the received file,
* certificate, certification chain and digital signature and their validity.
*
* 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 SignedFileUploadAction extends Action {
public ActionForward perform(ActionMapping aActionMapping, ActionForm
aActionForm, HttpServletRequest aRequest, HttpServletResponse aResponse) {
SignedFileUploadActionForm signedFileUploadActionForm =
(SignedFileUploadActionForm) aActionForm;
HttpSession session = aRequest.getSession();
session.setAttribute(
"signedFileUploadActionForm", signedFileUploadActionForm);
return aActionMapping.findForward("ShowSignedFileUploadResults");
}
}
|
Всичко, което това събитие прави, е да запише получения action form обект с данните от формата в потребителската сесия под ключ “signedFileUploadActionForm” и след това да пренасочи изпълнението на уеб приложението към страницата за анализ на получения подписан файл ShowSignedFileUploadResults.jsp, която е описана в конфигурационния файл на Struts.
Страница за анализ на получените данни
Страницата за анализ на получения подписан файл е малко по-сложна от останалите. Тя извлича action form обекта от сесията на потребителя и след това анализира получените данни и показва информация за тях. Извършва се верификация на цифровия подпис на получения файл и верификация на получения цифров сертификат, използван за подписването. Ако е приложена сертификационна верига, и тя се верифицира. Ето сорс кода на страницата ShowSignedFileUploadResults.jsp:
ShowSignedFileUploadResults.jsp
|
<%
/**
* A JSP for verifying digital signature, certificate and certification chain of the
* received signed file. It assumes that the data, received by submitting some of
* the forms SignedFileUploadForm-PFX.jsp or SignedFileUploadForm-SmartCard.jsp
* stays in the user's session in SignedFileUploadActionForm object stored with the
* key "signedFileUploadActionForm".
*
* The trusted certificates used for direct certificate verification should be
* located in a directory whose name stays in the CERTS_FOR_DIRECT_VALIDATION_DIR
* constant (see the code below).
*
* The trusted root CA certificates used for certification chain verification should
* be located in a directory whose name stays in the TRUSTED_CA_ROOT_CERTS_DIR
* constant (see the code below).
*
* 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.
*/
%>
<%@page import="demo.*,
java.io.*,
java.util.*,
java.security.*,
java.security.cert.*,
org.apache.struts.upload.FormFile,
javax.servlet.jsp.JspWriter" %>
<%!
public static final String CERTS_FOR_DIRECT_VALIDATION_DIR =
"/WEB-INF/certs-for-direct-validation";
public static final String TRUSTED_CA_ROOT_CERTS_DIR =
"/WEB-INF/trusted-CA-root-certs";
private static final int KEY_USAGE_DIGITAL_SIGNATURE = 0;
private static final int KEY_USAGE_NON_REPUDIATION = 1;
private static final int KEY_USAGE_KEY_ENCIPHERMENT = 2;
private static final int KEY_USAGE_DATA_ENCIPHERMENT = 3;
private static final int KEY_USAGE_KEY_AGREEMENT = 4;
private static final int KEY_USAGE_CERT_SIGN = 5;
private static final int KEY_USAGE_CRL_SIGN = 6;
private static final int KEY_USAGE_ENCIPHER_ONLY = 7;
private static final int KEY_USAGE_DECIPHER_ONLY = 8;
private ServletContext mApplicationContext = null;
private JspWriter mOut = null;
private SignedFileUploadActionForm mActionForm = null;
private FormFile mReceivedFile = null;
private byte[] mReceivedFileData = null;
private CertPath mCertPath = null;
private X509Certificate[] mCertChain = null;
private X509Certificate mCertificate = null;
private byte[] mSignature = null;
private String mSignatureBase64Encoded;
%>
<html>
<body>
<%
mApplicationContext = application;
mOut = out;
mActionForm = (SignedFileUploadActionForm)
session.getAttribute("signedFileUploadActionForm");
if (mActionForm == null) {
// User session does not contain the SignedFileUploadActionForm object
%>
Please choose file for signing first.
<%
} else {
try {
// Analyse received signed file and display information about it
processReceivedFile();
displayFileInfo(mReceivedFile, mReceivedFileData);
mOut.println(" ");
// Analyse received certification chain
processReceivedCertificationChain();
// Analyse received digital signature
processReceivedSignature();
// Display signature, verify it and display the verification results
displaySignature(mSignatureBase64Encoded);
verifyReceivedSignature();
mOut.println(" ");
// Display certificate, verify it and display the results
displayCertificate(mCertificate);
verifyReceivedCertificate();
mOut.println(" ");
// Display certification chain, verify it and display the results
displayCertificationChain(mCertChain);
verifyReceivedCertificationChain();
} catch (Exception e) {
// Error occurred. Display the exception with its full stack trace
out.println("
Error: ");
e.printStackTrace(new PrintWriter(out));
out.println("
");
}
}
%>
<br/>
Go to the <a href="index.html">start pagea>.
body>
html>
<%!
/**
* Extracts the received file and its data from the received HTML form data.
* The extracted file and its content are stored in the member variables
* mReceivedFile and mReceivedFileData.
* @throws Exception if no file is received.
*/
private void processReceivedFile()
throws Exception {
mReceivedFile = mActionForm.getUploadFile();
if (mReceivedFile == null) {
throw new Exception("No file received. Please upload some file.");
}
mReceivedFileData = mReceivedFile.getFileData();
}
/**
* Displays information about give file. Displays its file name and file size.
*/
private void displayFileInfo(FormFile aFile, byte[] aFileData)
throws Exception {
String fileName = aFile.getFileName();
mOut.println("Signed file successfully uploaded.
");
mOut.println("File name: " + fileName + "
");
mOut.println("File size: " + aFileData.length + " bytes.
");
}
/**
* Analyses received certification chain and extracts the certificates that it
* consist of. The certification chain should be PkiPath-encoded (ASN.1 DER
* formatted), stored as Base64-string. The extracted chain is stored in the
* member variables mCertPath as a CertPath object and in mCertChain as array
* of X.509 certificates. Also the certificate used for signing the received
* file is extracted in the member variable mCertificate.
* @throws Exception if the received certification chain can not be decoded
* (i.e. its encoding or internal format is invalid).
*/
private void processReceivedCertificationChain()
throws Exception {
String certChainBase64Encoded = mActionForm.getCertChain();
try {
mCertPath = DigitalSignatureUtils.loadCertPathFromBase64String(
certChainBase64Encoded);
List certsInChain = mCertPath.getCertificates();
mCertChain = (X509Certificate[])
certsInChain.toArray(new X509Certificate[0]);
mCertificate = mCertChain[0];
}
catch (Exception e) {
throw new Exception("Invalid certification chain received.", e);
}
}
/**
* Displays given certification chain. Displays the length of the chain and the
* subject distinguished names of all certificates in the chain, starting from
* the first and finishing to the last.
*/
private void displayCertificationChain(X509Certificate[] aCertChain)
throws IOException {
mOut.println("Certification chain length: " + aCertChain.length + "
");
for (int i=0; iPrincipal certPrincipal = aCertChain[i].getSubjectDN();
mOut.println("certChain[" + i + "] = " + certPrincipal + "
");
}
}
/**
* Analyses received Base64-encoded digital signature, decodes it and stores it
* in the member variable mSignature.
* @throws Exception if the received signature can not be decoded.
*/
private void processReceivedSignature()
throws Exception {
mSignatureBase64Encoded = mActionForm.getSignature();
try {
mSignature = Base64Utils.base64Decode(mSignatureBase64Encoded);
} catch (Exception e) {
throw new Exception("Invalid signature received.", e);
}
}
/**
* Displays given Base64-encoded digital signature.
*/
private void displaySignature(String aSignatureBase64Encoded)
throws IOException {
mOut.println("Digital signature (Base64-encoded): " +
aSignatureBase64Encoded);
}
/**
* Verifies the received signature using the received file data and certificate
* and displays the verification results. The received document, certificate and
* signature are taken from the member variables mReceivedFileData, mCertificate
* and mSignature respectively.
*/
private void verifyReceivedSignature()
throws IOException {
mOut.println("Digital signature status: ");
try {
boolean signatureValid = DigitalSignatureUtils.verifyDocumentSignature(
mReceivedFileData, mCertificate, mSignature);
if (signatureValid)
mOut.println("Signature is verified to be VALID.");
else
mOut.println("Signature is INVALID!");
} catch (Exception e) {
e.printStackTrace();
mOut.println("Signature verification failed due to exception: " +
e.toString());
}
mOut.println("");
}
/**
* Displays information about given certificate. This information includes the
* certificate subject distinguished name and its purposes (public key usages).
*/
private void displayCertificate(X509Certificate aCertificate)
throws IOException {
String certificateSubject = aCertificate.getSubjectDN().toString();
mOut.println("Certificate subject: " + certificateSubject + "
");
boolean[] certKeyUsage = aCertificate.getKeyUsage();
mOut.println("Certificate purposes (public key usages):
");
if (certKeyUsage != null) {
if (certKeyUsage[KEY_USAGE_DIGITAL_SIGNATURE])
mOut.println("[digitalSignature] - verify digital signatures
");
if (certKeyUsage[KEY_USAGE_NON_REPUDIATION])
mOut.println("[nonRepudiation] - verify non-repudiation
");
if (certKeyUsage[KEY_USAGE_KEY_ENCIPHERMENT])
mOut.println("[keyEncipherment] - encipher keys for transport
");
if (certKeyUsage[KEY_USAGE_DATA_ENCIPHERMENT])
mOut.println("[dataEncipherment] - encipher user data
");
if (certKeyUsage[KEY_USAGE_KEY_AGREEMENT])
mOut.println("[keyAgreement] - use for key agreement
");
if (certKeyUsage[KEY_USAGE_CERT_SIGN])
mOut.println("[keyCertSign] - verify signatures on certs
");
if (certKeyUsage[KEY_USAGE_CRL_SIGN])
mOut.println("[cRLSign] - verify signatures on CRLs
");
if (certKeyUsage[KEY_USAGE_ENCIPHER_ONLY])
mOut.println("[encipherOnly] - encipher during key agreement
");
if (certKeyUsage[KEY_USAGE_DECIPHER_ONLY])
mOut.println("[decipherOnly] - decipher during key agreement
");
} else {
mOut.println("[No purposes defined]
");
}
}
/**
* Verifies received certificate directly and displays the verification results.
* The certificate for verification is taken form mCertificate member variable.
* Trusted certificates are taken from the CERTS_FOR_DIRECT_VALIDATION_DIR
* directory. This directory should be relative to the Web application root
* directory and should contain only .CER files (DER-encoded X.509 cert.).
*/
private void verifyReceivedCertificate()
throws IOException, GeneralSecurityException {
// Create the list of the trusted certificates for direct validation
X509Certificate[] trustedCertificates =
getCertificateList(mApplicationContext,CERTS_FOR_DIRECT_VALIDATION_DIR);
// Verify the certificate and display the verification results
mOut.println("Certificate direct verification status: ");
try {
DigitalSignatureUtils.verifyCertificate(
mCertificate, trustedCertificates);
mOut.println("Certificate is verified to be VALID.");
} catch (CertificateExpiredException cee) {
mOut.println("Certificate is INVALID (validity period expired)!");
} catch (CertificateNotYetValidException cnyve) {
mOut.println("Certificate is INVALID (validity period not started)!");
} catch (DigitalSignatureUtils.CertificateValidationException cve) {
mOut.println("Certificate is INVALID! " + cve.getMessage());
}
mOut.println("");
}
/**
* Verifies received certification chain and displays the verification results.
* The chain for verification is taken form mCertPath member variable. Trusted
* CA root certificates are taken from the TRUSTED_CA_ROOT_CERTS_DIR directory.
* This directory should be relative to the Web application root directory and
* should contain only .CER files (DER-encoded X.509 certificates).
*/
private void verifyReceivedCertificationChain()
throws IOException, GeneralSecurityException {
// Create the most trusted CA set of trust anchors
X509Certificate[] trustedCACerts =
getCertificateList(mApplicationContext, TRUSTED_CA_ROOT_CERTS_DIR);
// Verify the certification chain and display the verification results
mOut.println("Certification chain verification: ");
try {
DigitalSignatureUtils.verifyCertificationChain(
mCertPath, trustedCACerts);
mOut.println("Certification chain verified to be VALID.");
} catch (CertPathValidatorException cpve) {
mOut.println("Certification chain is INVALID! Validation failed on " +
"cert [" + cpve.getIndex() + "] from the chain: "+cpve.toString());
}
mOut.println("
");
}
/**
* @return a list of X509 certificates, obtained by reading all files from the
* given directory. The supplied directory should be a given as a relative path
* from the Web appication root (e.g. "/WEB-INF/test") and should contain only
* .CER files (DER-encoded X.509 certificates).
*/
private X509Certificate[] getCertificateList(ServletContext aServletContext,
String aCertificatesDirectory)
throws IOException, GeneralSecurityException {
// Get a list of all files in the given directory
Set trustedCertsResNames =
aServletContext.getResourcePaths(aCertificatesDirectory);
// Allocate an array for storing the certificates
int count = trustedCertsResNames.size();
X509Certificate[] trustedCertificates = new X509Certificate[count];
// Read all X.509 certificate files one by one into an array
int index = 0;
Iterator trustedCertsResourceNamesIter = trustedCertsResNames.iterator();
while (trustedCertsResourceNamesIter.hasNext()) {
String certResName = (String) trustedCertsResourceNamesIter.next();
InputStream certStream =
aServletContext.getResourceAsStream(certResName);
try {
X509Certificate trustedCertificate =
DigitalSignatureUtils.loadX509CertificateFromStream(certStream);
trustedCertificates[index] = trustedCertificate;
index++;
} finally {
certStream.close();
}
}
return trustedCertificates;
}
%>
| Как се обработва полученият подписан файл?
Първоначално се проверява дали в сесията е записан action form обектът, който съдържа изпратените от потребителя данни. Ако такъв обект няма, това означава, че данни не са получени и потребителят се съобщава, че трябва да зареди страницата за подписване и изпращане на файл. Ако action form обектът е намерен, от него се взимат името на файла и дължината му и се отпечатват.
След това се декодира получената от клиента сертификационна верига. Понеже тя е била кодирана в текстов вид за да може да бъде пренесена през поле от HTML формата, първо се получава оригиналният й бинарен вид чрез Base64 декодиране. След това от този бинарен вид се възстановява оригиналната последователност от X.509 сертификати като се има предвид, че кодирането е било извършено във формат PkiPath. От декодираната сертификационна верига се изважда сертификатът на потребителя, използван при подписването. Този сертификат е винаги първият сертификат от тази верига.
Следва декодиране на получената цифрова сигнатура за да се възстанови оригиналният й бинарен вид, след което се проверява дали тя е валидна. От сертификата на клиента се извлича публичният му ключ и с него се проверява дали получената сигнатура съответства на получения документ. На потребителя се отпечатва сигнатурата, в текстов вид, както е получена, заедно с резултата от нейната проверка.
След като се установи, че получената сигнатура е истинска, се преминава към проверка на получения сертификат. Първоначално се отпечатва информация за сертификата – кой е негов собственик и за какво е предназначен да бъде използван. След това този сертификат се проверява дали е валиден. Проверката се извършва директно, без да се използва сертификационната му верига. Това може да бъде особено полезно в случаите, когато сертификационна верига липсва.
Директната проверка използва съвкупност от доверени сертификати, която се прочита от специална директория, името на която се взима от константа в страницата. Тази директория трябва да е поддиректория на уеб приложението и трябва да съдържа само файлове със сертификати (.CER файлове). Ако се установи, че сертификатът на потребителя е директно подписан от някой от тези сертификати, той се счита за валиден. На потребителя се отпечатва резултатът от директната верификация.
Следва проверка на получената сертификационна верига. Преди да започне самата проверка веригата се отпечатва на потребителя във вид на последователност от сертификати, всеки на отделен ред. За всеки сертификат се отпечатва поредният му номер във веригата и информация за собственика му.
Проверката започва със зареждане на сертификатите, които ще бъдат използвани като крайни точки на доверие. Тези доверени Root-сертификати стоят във вид на .CER файлове в специална директория на уеб приложението, името на която се указва от константа.
Проверката използва средствата на Java Certification Path API и установява дали подадената верига е валидна. Ако веригата се състои от само един сертификат (т. е. верига реално няма), тя се счита за невалидна. Ако веригата се състои от повече от един сертификат, от нея се премахва последният сертификат и се изпълнява PKIX алгоритъма.
Резултатът от извършената верификация се отпечатва на потребителя. Ако се установи, че веригата е невалидна, на потребителя се отпечатва причината за това и поредният номер на сертификата, при проверката на който е възникнал проблемът.
За четенето на всички файлове от дадена директория се използват методите getResourcePaths() и getResourceAsStream() на класа ServletContext.
На всяка една стъпка от обработката на получения подписан файл, ако възникне проблемна ситуация, при която приложението не може да продължи работата си, на потребителя се отпечатва съобщение за грешка, придружено от пълния вид на полученото изключение.
Възможни са различни проблеми ситуации като например липса на файл, липса на сертификационна верига, липса на сигнатура, невалидно кодиране на получената сертификационна верига или сигнатура, липса на някоя от директориите с доверени сертификати, невалиден формат на сертификат и други.
Верификацията на цифрови подписи и сертификати в действие
Резултатът от всички описани проверки, които се извършват при получаване на подписан файл, изглежда по следния начин (фигура 4-7):
Фигура 4 24. Резултатът от верификация на цифров подпис и сертификат
Основната криптографска функционалност на приложението
Основната функционалност по верификацията на сигнатури, сертификати и сертификационни вериги се намира в класа DigitalSignatureUtils. Класът е базиран на средствата от Java Cryptography Architecture (JCA), Java Cryptography Extension и Java Certification Path API. Както и при аплета, реализацията на всички криптографски алгоритми се осигурява от доставчика на криптографски услуги по подразбиране SunJSSE, който е част от JDK 1.4. Ето и сорс кода на класа:
DigitalSignatureUtils.java
|
package demo;
import java.security.PublicKey;
import java.security.Signature;
import java.security.GeneralSecurityException;
import java.security.cert.*;
import java.io.*;
import java.util.List;
import java.util.HashSet;
/**
* Utility class for digital signatures and certificates verification.
*
* Verification of digital signature aims to confirm or deny that given signature is
* created by signing given document with the private key corresponding to given
* certificate. Verification of signatures is done with the standard digital
* signature verification algorithm, provided by Java Cryptography API:
* 1. The message digest is calculated from given document.
* 2. The original message digest is obtained by decrypting the signature with
* the public key of the signer (this public key is taken from the signer's
* certificate).
* 3. Values calculated in step 1. and step 2. are compared.
*
* Verification of a certificate aims to check if the certificate is valid wihtout
* inspecting its certification chain (sometimes it is unavailable). The certificate
* verification is done in two steps:
* 1. The certificate validity period is checked against current date.
* 2. The certificate is checked if it is directly signed by some of the trusted
* certificates that we have. A list of trusted certificates is supported for this
* direct certificate verification process. If we want to successfully validate the
* certificates issued by some certification authority (CA), we need to add the
* certificate of this CA in our trusted list. Note that some CA have several
* certificates and we should add only that of them, which the CA directly uses for
* issuing certificates to its clients.
*
* Verification of a certification chains aims to check if given certificate is
* valid by analysing its certification chain. A certification chain always starts
* with the user certificate that should be verified, then several intermediate CA
* certificates follow and at the end of the chain stays some root CA certificate.
* The verification process includes following steps (according to PKIX algorithm):
* 1. Check the certificate validity period against current date.
* 2. Check if each certificate in the chain is signed by the previous.
* 3. Check if all the certificates in the chain, except the first, belong to
* some CA, i.e. if they are authorized to be used for signing other certificates.
* 4. Check if the root CA certificate in the end of the chain is trusted, i.e.
* if is it in the list of trusted root CA certificates.
* The verification process uses PKIX algorithm, defined in RFC-3280, but don't use
* CRL lists.
*
* 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 DigitalSignatureUtils {
private static final String X509_CERTIFICATE_TYPE = "X.509";
private static final String CERT_CHAIN_ENCODING = "PkiPath";
private static final String DIGITAL_SIGNATURE_ALGORITHM_NAME = "SHA1withRSA";
private static final String CERT_CHAIN_VALIDATION_ALGORITHM = "PKIX";
/**
* Loads X.509 certificate from DER-encoded binary stream.
*/
public static X509Certificate loadX509CertificateFromStream(
InputStream aCertStream)
throws GeneralSecurityException {
CertificateFactory cf=CertificateFactory.getInstance(X509_CERTIFICATE_TYPE);
X509Certificate cert = (X509Certificate)cf.generateCertificate(aCertStream);
return cert;
}
/**
* Loads X.509 certificate from DER-encoded binary file (.CER file).
*/
public static X509Certificate loadX509CertificateFromCERFile(String aFileName)
throws GeneralSecurityException, IOException {
FileInputStream fis = new FileInputStream(aFileName);
X509Certificate cert = null;
try {
cert = loadX509CertificateFromStream(fis);
} finally {
fis.close();
}
return cert;
}
/**
* Loads a certification chain from given Base64-encoded string, containing
* ASN.1 DER formatted chain, stored with PkiPath encoding.
*/
public static CertPath loadCertPathFromBase64String(
String aCertChainBase64Encoded)
throws CertificateException, IOException {
byte[] certChainEncoded = Base64Utils.base64Decode(aCertChainBase64Encoded);
CertificateFactory cf=CertificateFactory.getInstance(X509_CERTIFICATE_TYPE);
InputStream certChainStream = new ByteArrayInputStream(certChainEncoded);
CertPath certPath;
try {
certPath = cf.generateCertPath(certChainStream, CERT_CHAIN_ENCODING);
} finally {
certChainStream.close();
}
return certPath;
}
/**
* Verifies given digital singature. Checks if given signature is obtained by
* signing given document with the private key corresponing to given public key.
*/
public static boolean verifyDocumentSignature(byte[] aDocument,
PublicKey aPublicKey, byte[] aSignature)
throws GeneralSecurityException {
Signature signatureAlgorithm =
Signature.getInstance(DIGITAL_SIGNATURE_ALGORITHM_NAME);
signatureAlgorithm.initVerify(aPublicKey);
signatureAlgorithm.update(aDocument);
boolean valid = signatureAlgorithm.verify(aSignature);
return valid;
}
/**
* Verifies given digital singature. Checks if given signature is obtained
* by signing given document with the private key corresponing to given
* certificate.
*/
public static boolean verifyDocumentSignature(byte[] aDocument,
X509Certificate aCertificate, byte[] aSignature)
throws GeneralSecurityException {
PublicKey publicKey = aCertificate.getPublicKey();
boolean valid = verifyDocumentSignature(aDocument, publicKey, aSignature);
return valid;
}
/**
* Verifies a certificate. Checks its validity period and tries to find a
* trusted certificate from given list of trusted certificates that is directly
* signed given certificate. The certificate is valid if no exception is thrown.
*
* @param aCertificate the certificate to be verified.
* @param aTrustedCertificates a list of trusted certificates to be used in
* the verification process.
*
* @throws CertificateExpiredException if the certificate validity period is
* expired.
* @throws CertificateNotYetValidException if the certificate validity period is
* not yet started.
* @throws CertificateValidationException if the certificate is invalid (can not
* be validated using the given set of trusted certificates.
*/
public static void verifyCertificate(X509Certificate aCertificate,
X509Certificate[] aTrustedCertificates)
throws GeneralSecurityException {
// First check certificate validity period
aCertificate.checkValidity();
// Check if the certificate is signed by some of the given trusted certs
for (int i=0; iX509Certificate trustedCert = aTrustedCertificates[i];
try {
aCertificate.verify(trustedCert.getPublicKey());
// Found parent certificate. Certificate is verified to be valid
return;
}
catch (GeneralSecurityException ex) {
// Certificate is not signed by current trustedCert. Try the next
}
}
// Certificate is not signed by any of the trusted certs --> it is invalid
throw new CertificateValidationException(
"Can not find trusted parent certificate.");
}
/**
* Verifies certification chain using "PKIX" algorithm, defined in RFC-3280.
* It is considered that the given certification chain start with the target
* certificate and finish with some root CA certificate. The certification
* chain is valid if no exception is thrown.
*
* @param aCertChain the certification chain to be verified.
* @param aTrustedCACertificates a list of most trusted root CA certificates.
* @throws CertPathValidatorException if the certification chain is invalid.
*/
public static void verifyCertificationChain(CertPath aCertChain,
X509Certificate[] aTrustedCACertificates)
throws GeneralSecurityException {
int chainLength = aCertChain.getCertificates().size();
if (chainLength < 2) {
throw new CertPathValidatorException("The certification chain is too " +
"short. It should consist of at least 2 certiicates.");
}
// Create a set of trust anchors from given trusted root CA certificates
HashSet trustAnchors = new HashSet();
for (int i = 0; i < aTrustedCACertificates.length; i++) {
TrustAnchor trustAnchor =
new TrustAnchor(aTrustedCACertificates[i], null);
trustAnchors.add(trustAnchor);
}
// Create a certification chain validator and a set of parameters for it
PKIXParameters certPathValidatorParams = new PKIXParameters(trustAnchors);
certPathValidatorParams.setRevocationEnabled(false);
CertPathValidator chainValidator =
CertPathValidator.getInstance(CERT_CHAIN_VALIDATION_ALGORITHM);
// Remove the root CA certificate from the end of the chain. It is required
// by the validation algorithm because by convention the trust anchor
// certificates should not be a part of the chain that is validated
CertPath certChainForValidation = removeLastCertFromCertChain(aCertChain);
// Execute the certification chain validation
chainValidator.validate(certChainForValidation, certPathValidatorParams);
}
/**
* Removes the last certificate from given certification chain.
* @return given cert chain without the last certificate in it.
*/
private static CertPath removeLastCertFromCertChain(CertPath aCertChain)
throws CertificateException {
List certs = aCertChain.getCertificates();
int certsCount = certs.size();
List certsWithoutLast = certs.subList(0, certsCount-1);
CertificateFactory cf=CertificateFactory.getInstance(X509_CERTIFICATE_TYPE);
CertPath certChainWithoutLastCert = cf.generateCertPath(certsWithoutLast);
return certChainWithoutLastCert;
}
/**
* Exception class for certificate validation errors.
*/
public static class CertificateValidationException
extends GeneralSecurityException {
public CertificateValidationException(String aMessage) {
super(aMessage);
}
}
}
| Как работи основната криптографска функционалност?
Класът започва с методи за зареждане на сертификат от поток и от файл, с които се прочитат файловете с доверените сертификати, използвани при проверката на сертификати и сертификационни вериги. Очаква се тези файлове да бъдат в стандартния .CER формат (ASN.1 DER-кодирани).
Следва метод за зареждане на сертификационна верига, представена във формат PkiPath и кодирана в текстов вид с кодиране Base64.
Класът предлага още функционалност за проверка на цифрови сигнатури, която използва алгоритъма SHA1withRSA – същият, който се използва от аплета за подписване.
Предоставят се още методи за директна верификация на сертификат и верификация на сертификационни вериги.
Методът за директна верификация на сертификат като параметър очаква сертификата и множество от доверени сертификати, сред които да търси издателя на проверявания сертификат. Верификацията е успешна, ако методът завърши изпълнението си без да генерира изключение.
Методът за проверката на сертификационни вериги е малко по-сложен. Той приема като вход сертификационна верига и множество от доверени Root-сертификати.
При проверката на подадената верига първоначално се проверява дали тя се състои от поне 2 сертификата. Верига от 1 сертификат не може да бъде валидна, защото тя трябва да завършва с Root-сертификата на някой сертификационен орган от първо ниво, а в същото време тя започва с потребителския сертификат.
Проверката започва с построяване на множество от крайни точки на доверие (TrustAnchor обекти) от зададените доверени Root-сертификати. След това се създава обект, съдържащ множеството от параметри на алгоритъма за проверка. От тези параметри се указва на алгоритъма да не използва списъци от анулирани сертификати (CRLs). Използването на такива CRL списъци не се прилага, защото е сложно за реализация и изисква допълнителни усилия за извличане на тези списъци от сървърите на сертификационните органи, които ги издават и разпространяват.
След създаването на крайните точки на доверите и инициализирането на параметрите на алгоритъма за верификация има още една важна стъпка преди самата верификация. Алгоритъмът PKIX, който се използва за верификацията има една особеност. Той очаква да му се подаде сертификационна верига, която не завършва с Root-сертификат на някой сертификационен орган, а със сертификата, който стои непосредствено преди него, т. е. очаква от сертификационната верига да бъде отстранен последният сертификат. Ако последният сертификат от веригата не бъде отстранен преди проверката, верификацията няма да е успешна.
Ако веригата не е валидна, се генерира изключение, в което се указва поредният номер на сертификата, в който е възникнал проблемът.
За работа с Base64-кодирана информация в уеб приложението се използва същият клас Base64Utils, който се използва и при аплета за подписване на файлове.
Конфигурационен файл на уеб приложението
Съгласно стандартите на платформата J2EE за работата на демонстрационното уеб приложение е необходим още конфигурационният файл:
web.xml
|
xml version="1.0" encoding="UTF-8"?>
2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>actionservlet-name>
<servlet-class>org.apache.struts.action.ActionServletservlet-class>
<init-param>
<param-name>configparam-name>
<param-value>/WEB-INF/struts-config.xmlparam-value>
init-param>
<load-on-startup>2load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>actionservlet-name>
<url-pattern>*.dourl-pattern>
servlet-mapping>
<welcome-file-list>
<welcome-file>/index.htmlwelcome-file>
welcome-file-list>
web-app>
|
Този файл съдържа настройките на приложението и указва, да бъде зареден и инициализиран Struts framework и неговият ActionServlet. Като стартова страница по подразбиране е указан файлът index.html от главната директория на приложението.
Стартова страница на приложението
Стартовата страница index.html е изключително проста. Тя съдържа просто две препратки – към страницата за подписване с PKCS#12 хранилище и към страницата за подписване със смарт карта. Ето нейният код:
index.html
|
* 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.
-->
<html>
<body>
<a href="SignedFileUploadForm-PFX.jsp">Digital document signing with .PFX file
(PKCS#12 keystore) demo.a>
<br>
<a href="SignedFileUploadForm-SmartCard.jsp">Digital document signing with
smart card (PKCS#11 token) demo.a>
body>
html>
|
Когато демонстрационното уеб приложение се стартира първоначално, то зарежда именно тази страница и дава възможност на потребителя да тества съответно двата аплета за подписване на документи.
За да се използва Struts framework е необходим и неговият конфигурационен файл struts-config.xml:
struts-config.xml
|
xml version="1.0" encoding="UTF-8"?>
<struts-config>
<form-beans>
<form-bean name="SignedFileUploadActionForm"
type="demo.SignedFileUploadActionForm"/>
form-beans>
<action-mappings>
<action name="SignedFileUploadActionForm"
type="demo.SignedFileUploadAction"
scope="request" path="/SignedFileUpload">
<forward name="ShowSignedFileUploadResults"
path="/ShowSignedFileUploadResults.jsp" redirect="true"/>
action>
action-mappings>
struts-config>
|
Този файл конфигурира Struts формите и Struts action класовете, които се използват от уеб приложението.
Скрипт за компилиране и построяване на приложението
Съвкупността от всички описани файлове съставя демонстрационното уеб приложение. За да го компилираме и подготвим за изпълнение можем да използваме следния Apache Ant скрипт:
build.xml
|
xml version="1.0" encoding="iso-8859-1"?>
<project name="DocumentSigningDemoWebApp" default="build" basedir=".">
<target name="init">
<property name="app-name" value="DocumentSigningDemoWebApp"/>
<property name="webapp-name" value="${app-name}.war"/>
<property name="src-dir" value="src"/>
<property name="www-dir" value="wwwroot"/>
<property name="classes-dir" value="${www-dir}/WEB-INF/classes"/>
<property name="web-xml" value="${www-dir}/WEB-INF/web.xml"/>
<property name="lib-dir" value="${www-dir}/WEB-INF/lib"/>
<property name="deploy-dir" value="deploy"/>
target>
<target name="clean" depends="init">
<delete dir="${classes-dir}"/>
<mkdir dir="${classes-dir}"/>
<delete dir="${deploy-dir}"/>
<mkdir dir="${deploy-dir}"/>
target>
<target name="compile" depends="init">
<javac srcdir="src"
destdir="wwwroot/WEB-INF/classes"
debug="on">
<classpath>
<fileset dir="${lib-dir}">
<include name="**/*.jar"/>
<include name="**/*.zip"/>
fileset>
classpath>
javac>
target>
<target name="war" depends="init">
<war compress="true" destfile="${deploy-dir}/${webapp-name}"
webxml="${web-xml}">
<fileset dir="${www-dir}">
<include name="**/*.*"/>
fileset>
war>
target>
<target name="build">
<antcall target="clean"/>
<antcall target="compile"/>
<antcall target="war"/>
target>
project>
|
Скриптът компилира файловете на приложението и пакетира всички негови части (JSP файлове, ресурси, библиотеки, компилирани класове и др.) в архив като подрежда файловете и директориите по специален начин, съгласно J2EE спецификациите за уеб приложения.
Резултатът от изпълнението с инструмента ant на този скрипт е файлът DocumentSigningDemoWebApp.war, който съдържа компилираното уеб приложение във вид готов за изпълнение върху стандартен уеб контейнер.
Инсталиране (deployment) на приложението
За да изпълним приложението DocumentSigningDemoWebApp.war е необходимо да го инсталираме (deployment) на някой J2EE сървър или сървър за Java уеб приложения (Servlet container).
Ако използваме сървъра за J2EE уеб приложения Apache Tomcat, е достатъчно до копираме файла DocumentSigningDemoWebApp.war в директория %TOMCAT_HOME%/webapps и да стартираме Tomcat. След това (при стандартна инсталация и конфигурация на Tomcat) приложението е достъпно от адрес: http://localhost:8080/DocumentSigningDemoWebApp/ (фигура 4-8):
Фигура 4 25. Уеб приложението DocumentSigningDemoWebApp
|
Национална академия по разработка на софтуер
|
Лекторите
» Светлин Наков е преподавател по съвременни софтуерни технологии в СУ “Св. Климент Охридски”.
Той е автор на десетки научни и технически публикации и няколко книги, свързани с разработката на софтуер, заради което е търсен лектор и консултант.
През 2004 г. получава наградата "Джон Атанасов" от президента на България Георги Първанов за приноса му към развитието на информационните технологии и информационното общество.
» Мартин Кулов е изпълнителен директор във фирма “Код Атест”, където разработва проекти за повишаване качеството на софтуерните продукти в България чрез автоматизация на процесите и внедряване на системи за управление на качеството.
Мартин е опитен лектор и сертифициран от Майкрософт разработчик по програмите MCSD и MCSD.NET.
» Други инструктори с опит като преподаватели и програмисти.
|
Академията
» Национална академия по разработка на софтуер (НАРС) е център за професионално обучение на софтуерни специалисти.
» НАРС провежда задълбочени курсове по разработка на софтуер и съвременни софтуерни технологии.
» Предлагани специалности:
.NET Enterprise Developer
Java Enterprise Developer
» Качествено обучение с много практически упражнения
» Завършвате само за 3 месеца.
» Гарантирана работа след успешно завършване!
» Професионална сертификация!
» БЕЗПЛАТНО!
Учите безплатно, плащате след като завършите и започнете работа.
Стипендии от софтуерни фирми.
|
http://academy.devbg.org
|
Сподели с приятели: |