Java tutorial
/* * SD-DSS-Util, a Utility Library and a Command Line Interface for SD-DSS. * Copyright (C) 2013 La Traccia http://www.latraccia.it/en/ * Developed by Francesco Pontillo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see [http://www.gnu.org/licenses/]. */ package it.latraccia.dss.util.builder; import eu.europa.ec.markt.dss.applet.main.FileType; import eu.europa.ec.markt.dss.applet.util.SigningUtils; import eu.europa.ec.markt.dss.common.SignatureTokenType; import eu.europa.ec.markt.dss.exception.BadPasswordException; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.signature.DSSDocument; import eu.europa.ec.markt.dss.signature.SignatureFormat; import eu.europa.ec.markt.dss.signature.SignaturePackaging; import eu.europa.ec.markt.dss.signature.SignatureParameters; import eu.europa.ec.markt.dss.signature.token.DSSPrivateKeyEntry; import eu.europa.ec.markt.dss.signature.token.SignatureTokenConnection; import eu.europa.ec.markt.dss.validation.TrustedListCertificateVerifier; import it.latraccia.dss.util.builder.format.FormatBuilder; import it.latraccia.dss.util.builder.policy.PolicyBuilder; import it.latraccia.dss.util.builder.token.TokenBuilder; import it.latraccia.dss.util.entity.DigestAlgorithm; import it.latraccia.dss.util.entity.level.SignaturePAdESLevel; import it.latraccia.dss.util.exception.*; import it.latraccia.dss.util.model.SignatureCLIModel; import it.latraccia.dss.util.util.AssertHelper; import it.latraccia.dss.util.util.Util; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.List; /** * Custom builder for signing a document with given parameters. * When finished setting all of the parameters, call {@link SignatureBuilder#build()}. * <p/> * Date: 06/12/13 * Time: 9.37 * * @author Francesco Pontillo */ public class SignatureBuilder implements IBuilder<File> { // All of the signature parameters private String source; private String target; private FormatBuilder formatBuilder; // This is a builder private FormatBuilder.SignatureFormatLevelPackaging signatureFormatLevelPackaging; private TokenBuilder tokenBuilder; // This is a builder private TokenBuilder.SignatureToken token; private PolicyBuilder policyBuilder; // This is a builder private PolicyBuilder.SignaturePolicy policy; private IDSSPrivateKeyChooser privateKeyChooser; private DigestAlgorithm digestAlgorithm; private String claimedRole; // This model will be built and signed on build() private SignatureCLIModel model; public SignatureBuilder() { model = new SignatureCLIModel(); } public SignatureBuilder setSource(String file) { this.source = file; return this; } public SignatureBuilder setSource(File file) { this.source = file.getAbsolutePath(); return this; } public SignatureBuilder setTarget(String file) { this.target = file; return this; } public SignatureBuilder setTarget(File file) { this.target = file.getAbsolutePath(); return this; } public SignatureBuilder setFormatBuilder(FormatBuilder formatBuilder) { this.formatBuilder = formatBuilder; return this; } public SignatureBuilder setDigestAlgorithm(DigestAlgorithm digestAlgorithm) { if (digestAlgorithm == null) { digestAlgorithm = DigestAlgorithm.SHA1; } this.digestAlgorithm = digestAlgorithm; return this; } public SignatureBuilder setTokenBuilder(TokenBuilder tokenBuilder) { this.tokenBuilder = tokenBuilder; return this; } public SignatureBuilder setDSSPrivateKeyChooser(IDSSPrivateKeyChooser chooser) { this.privateKeyChooser = chooser; return this; } public SignatureBuilder setClaimedRole(String claimedRole) { this.claimedRole = claimedRole; return this; } public SignatureBuilder setPolicyBuilder(PolicyBuilder policyBuilder) { this.policyBuilder = policyBuilder; return this; } private File simulate() throws IOException, SignatureException, KeyStoreException, NoSuchAlgorithmException { return build(true); } public File build() throws IOException, SignatureException, KeyStoreException, NoSuchAlgorithmException { return build(false); } public File build(boolean simulate) throws SignatureSourceFileNotFoundException, SignatureFormatMismatchException, SignatureLevelMismatchException, SignaturePackagingMismatchException, SignatureServiceUrlException, SignatureTokenException, KeyStoreException, BadPasswordException, SignaturePolicyAlgorithmMismatchException, SignaturePolicyLevelMismatch, SignatureTargetFileException, NoSuchAlgorithmException, SignatureMoccaAlgorithmMismatchException, SignatureMoccaUnavailabilityException { InnerSignatureBuilder innerBuilder = new InnerSignatureBuilder(); // Set the source file innerBuilder.setSourceFile(source, model); innerBuilder.validateSourceFile(model); // Set the signature format, level and packaging innerBuilder.setSignatureFormatLevelPackaging(formatBuilder, model); // The format must match the file type innerBuilder.validateSignatureFormatFileTypeMatch(model); // The packaging must match the file type innerBuilder.validateSignaturePackagingFileTypeMatch(model); // The service URL must be set if some parameters are innerBuilder.validateServiceUrl(model); // Set the digest algorithm innerBuilder.setDigestAlgorithm(digestAlgorithm, model); // Set the token and token parameters try { innerBuilder.setToken(tokenBuilder, model); } catch (FileNotFoundException e) { throw new SignatureTokenException(e); } // Ensure that a key chooser has been selected innerBuilder.validatePrivateKeyChooser(); // Open the connection to the selected keystore and set the chosen key innerBuilder.setPrivateKey(model); // Set the claimed role into the model innerBuilder.setClaimedRole(claimedRole, model); // Set the explicit policy innerBuilder.setPolicy(policyBuilder, model); // Validate explicit policy parameters innerBuilder.validatePolicy(model); // Set the target file innerBuilder.setTargetFile(target, model); File signedFile = null; if (!simulate) { signedFile = innerBuilder.signDocument(model); } return signedFile; } /** * Get the suggested target file name. * This method is a wrapper around the original {@link it.latraccia.dss.util.builder.SignatureBuilder#prepareTargetFileName(java.io.File, * eu.europa.ec.markt.dss.signature.SignaturePackaging, String)}. * * @return The suggested target file name */ private String getSuggestedFileName() { return prepareTargetFileName(new File(source), SignaturePackaging.valueOf(signatureFormatLevelPackaging.getPackaging().getValue()), signatureFormatLevelPackaging.getFormat().getValue()).getName(); } /** * Suggest the target file name. * Original code in {@link eu.europa.ec.markt.dss.applet.wizard.signature.SaveStep#prepareTargetFileName(java.io.File, * eu.europa.ec.markt.dss.signature.SignaturePackaging, String)} * * @param file The selected file to sign * @param signaturePackaging The selected packaging * @param signatureFormat The complete signature format (e.g. "CAdES") * @return The suggested target File */ private File prepareTargetFileName(final File file, final SignaturePackaging signaturePackaging, final String signatureFormat) { final File parentDir = file.getParentFile(); final String originalName = StringUtils.substringBeforeLast(file.getName(), "."); final String originalExtension = "." + StringUtils.substringAfterLast(file.getName(), "."); final String format = signatureFormat.toUpperCase(); if ((SignaturePackaging.ENVELOPING == signaturePackaging || SignaturePackaging.DETACHED == signaturePackaging) && format.startsWith("XADES")) { return new File(parentDir, originalName + "-signed" + ".xml"); } if (format.startsWith("CADES") && !originalExtension.toLowerCase().equals(".p7m")) { return new File(parentDir, originalName + originalExtension + ".p7m"); } if (format.startsWith("ASIC")) { return new File(parentDir, originalName + originalExtension + ".asics"); } return new File(parentDir, originalName + "-signed" + originalExtension); } public interface IDSSPrivateKeyChooser { public DSSPrivateKeyEntry getDSSPrivateKey(List<DSSPrivateKeyEntry> keys); } protected class InnerSignatureBuilder { /** * Set the source file to be signed. * * @param model The signature model * @throws java.io.FileNotFoundException Thrown if the input file hasn't been found or it is not valid */ protected void setSourceFile(String sourceFile, SignatureCLIModel model) throws SignatureSourceFileNotFoundException { // Search in resources, then absolute path try { String foundFile = Util.getFileInAbsolutePathOrResources(sourceFile); model.setSelectedFile(new File(foundFile)); } catch (FileNotFoundException exception) { throw new SignatureSourceFileNotFoundException(exception); } } /** * Validate a source file. * Validation from {@link eu.europa.ec.markt.dss.applet.wizard.signature.FileStep#isValid()} * * @param model The signature model * @throws SignatureSourceFileNotFoundException Thrown if the file does not exists or the path is not a file */ protected void validateSourceFile(SignatureCLIModel model) throws SignatureSourceFileNotFoundException { if (!model.getSelectedFile().exists() || !model.getSelectedFile().isFile()) { throw new SignatureSourceFileNotFoundException( new FileNotFoundException("The source file was not found or it is not a valid file.")); } } /** * Set the signature format, level and packaging. * Some of the code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.SignatureStep#init()}. * * @param model The signature model * * @throws SignatureLevelMismatchException Thrown if there is a mismatch between the file extension and the * signature level * * @throws SignaturePackagingMismatchException Thrown if there is a mismatch between the file extension and the * signature packaging * @throws SignatureFormatMismatchException Thrown if there is a mismatch between the file extension and the * signature format */ protected void setSignatureFormatLevelPackaging(FormatBuilder formatBuilder, SignatureCLIModel model) throws SignatureLevelMismatchException, SignaturePackagingMismatchException, SignatureFormatMismatchException { // Build the object signatureFormatLevelPackaging = formatBuilder.build(); // Set the signature format String format = signatureFormatLevelPackaging.getFormat().getValue(); String level = signatureFormatLevelPackaging.getLevel().getValue(); String packaging = signatureFormatLevelPackaging.getPackaging().getValue(); if (format != null) { model.setFormat(format); if (packaging != null) { model.setPackaging(packaging); if (StringUtils.isNotEmpty(level)) { model.setSimpleLevel(level); } } } // Set the service URL and validate it model.setServiceURL(signatureFormatLevelPackaging.getServiceUrl()); } /** * Validate the signature format according to the signature format. * * @param model The signature model * @throws SignatureFormatMismatchException Thrown if there is a format mismatch */ protected void validateSignatureFormatFileTypeMatch(SignatureCLIModel model) throws SignatureFormatMismatchException { // If the file is not a PDF, the PAdES cannot be selected if (model.getFileType() != FileType.PDF) { if (!AssertHelper.stringMustNotEqual("signature level", model.getFormat(), "PAdES")) { throw new SignatureFormatMismatchException(); } } } /** * Validate the signature packaging according to the signature format. * * @param model The signature model * @throws SignaturePackagingMismatchException Thrown if there is a packaging mismatch */ protected void validateSignaturePackagingFileTypeMatch(SignatureCLIModel model) throws SignaturePackagingMismatchException { // Validate the packaging String signatureFormat = model.getFormat(); SignaturePackaging signaturePackaging = model.getPackaging(); if (signatureFormat.equals(it.latraccia.dss.util.entity.format.SignatureFormat.XAdES) && (model.getFileType() != FileType.XML) && (signaturePackaging.equals(signaturePackaging))) { throw new SignaturePackagingMismatchException(); } } /** * Validate the service URL by requiring it if PAdES is selected as format or * the level is any different then BES, EPES. * * @param model The signature model * @throws SignatureServiceUrlException Thrown if the service URL is required but was not specified */ protected void validateServiceUrl(SignatureCLIModel model) throws SignatureServiceUrlException { String serviceUrl = model.getServiceURL(); if (Util.isNullOrEmpty(serviceUrl)) { // If the format is PAdES or the level is not one of the accepted if (model.getFormat() .startsWith(it.latraccia.dss.util.entity.format.SignatureFormat.PAdES.getValue()) || !AssertHelper.isStringInList(model.getSimpleLevel(), new String[] { SignaturePAdESLevel.BES.getValue(), SignaturePAdESLevel.EPES.getValue() })) { throw new SignatureServiceUrlException(); } } } /** * Set the custom digest algorithm. * * @param model The signature model */ protected void setDigestAlgorithm(DigestAlgorithm digestAlgorithm, SignatureCLIModel model) { // Set the digest algorithm, to SHA1 if null if (digestAlgorithm != null) { model.setDigestAlgorithm(digestAlgorithm.getValue()); } else { model.setDigestAlgorithm(eu.europa.ec.markt.dss.DigestAlgorithm.SHA1); } } /** * Set the token (certificate) type; PKCS11, PKCS12, MOCCA, MSAPI. * Set some token-specific parameters for getting the certificate: * - PKCS11: library file and password * - PKCS12: certificate file and password * - MOCCA: signature algorithm * <p/> * Some of this code has been taken from: * - {@link eu.europa.ec.markt.dss.applet.wizard.signature.SignatureStep#getNextStep()} * - {@link eu.europa.ec.markt.dss.applet.wizard.signature.PKCS11Step#isValid()} * - {@link eu.europa.ec.markt.dss.applet.wizard.signature.PKCS12Step#isValid()} * * @param model The signature model */ protected void setToken(TokenBuilder tokenBuilder, SignatureCLIModel model) throws FileNotFoundException, SignatureMoccaAlgorithmMismatchException, SignatureMoccaUnavailabilityException { // Set the token file, password, do some pre-validation token = tokenBuilder.build(); SignatureTokenType tokenType = SignatureTokenType.valueOf(token.getTokenType().getValue()); // Set the token type model.setTokenType(tokenType); // If the algorithm is MOCCA, set the model MOCCA algorithm if (token.getTokenType().equals(it.latraccia.dss.util.entity.token.SignatureTokenType.MOCCA)) { model.setMoccaSignatureAlgorithm(token.getMoccaAlgorithm().getValue()); } switch (tokenType) { case PKCS11: // Set the PKCS11 library file model.setPkcs11File(token.getTokenAsset()); // Set the card encryption password model.setPkcs11Password(token.getPassword()); break; case PKCS12: // Set the PKCS12 file model.setPkcs12File(token.getTokenAsset()); // Set the file encryption password model.setPkcs12Password(token.getPassword()); break; case MOCCA: model.setMoccaSignatureAlgorithm(token.getMoccaAlgorithm().getValue().toLowerCase()); break; } } /** * Ensure that the private key chooser has been set. * * @throws java.lang.NullPointerException if the chooser was not set */ protected void validatePrivateKeyChooser() throws NullPointerException { if (privateKeyChooser == null) { throw new NullPointerException("No private key chooser has been set, can't proceed!"); } } /** * Open the connection to the keystore, read all of the keys and selects one. * This method executes code taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.CertificateStep#init()}. * If no exception is thrown, this method guarantees that a private key has been selected. * * @param model The signature model * @throws java.security.KeyStoreException If there are errors accessing the keystore */ protected void setPrivateKey(SignatureCLIModel model) throws KeyStoreException, BadPasswordException { // Create the connection to the keystore provider SignatureTokenConnection connection = model.createTokenConnection(); // Get the keys List<DSSPrivateKeyEntry> entries = connection.getKeys(); // Get the selected key to be used DSSPrivateKeyEntry key = privateKeyChooser.getDSSPrivateKey(entries); // Set the key to be used for the signing process model.setSelectedPrivateKey(key); } /** * Set the claimed role of the user. * * @param claimedRole The claimed role * @param model The signature model */ protected void setClaimedRole(String claimedRole, SignatureCLIModel model) { model.setClaimedRole(claimedRole); } /** * Set the user personal data. * Part of the code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.PersonalDataStep#init()}. * * @param policyBuilder The {@link it.latraccia.dss.util.builder.policy.PolicyBuilder} that will be built to get * the set values * @param model The signature model * @throws SignatureException Thrown if any of the policy parameters are invalid */ protected void setPolicy(PolicyBuilder policyBuilder, SignatureCLIModel model) throws SignaturePolicyAlgorithmMismatchException { // Default to no policy model.setSignaturePolicyCheck(false); policy = policyBuilder.build(); // If the explicit policy data are valid if (policy.getOid() != null && policy.getOid().trim().length() > 0 && policy.getPolicyAlgorithm() != null) { // Set the explicit policy model.setSignaturePolicyId(policy.getOid()); model.setSignaturePolicyAlgo(policy.getPolicyAlgorithm().getValue()); model.setSignaturePolicyValue(policy.getHash()); model.setSignaturePolicyCheck(true); } } /** * Validate the policy by checking that the signature level is different than BES. * Some of this code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.PersonalDataStep#init()}. * * @param model The signature model * @throws SignaturePolicyLevelMismatch Thrown if the signature level doesn't match */ protected void validatePolicy(SignatureCLIModel model) throws SignaturePolicyLevelMismatch { boolean levelBES = model.getSimpleLevel().equalsIgnoreCase("BES"); if (model.isSignaturePolicyCheck() && levelBES) { throw new SignaturePolicyLevelMismatch(); } } /** * Sets the output file path, starting from the original file name and path, the signature format, level and * packaging. * Some of the called code has been taken from * {@link eu.europa.ec.markt.dss.applet.wizard.signature.SaveStep#prepareTargetFileName(java.io.File, * eu.europa.ec.markt.dss.signature.SignaturePackaging, String)}. * * @param targetFile The target file to set * @param model The signature model */ protected void setTargetFile(String targetFile, SignatureCLIModel model) { // The output path if and as requested by the user String destination = targetFile; File outputDir; String outputFile; File destinationFile; // If the destination wasn't set if (Util.isNullOrEmpty(destination)) { // Use the parent dir of the original file outputDir = model.getSelectedFile().getParentFile(); // Use the suggested file name outputFile = getSuggestedFileName(); destinationFile = new File(outputDir, outputFile); } else { // Tries to understand what destination is File outFileOrDir = new File(destination); // If the user requested a directory if (outFileOrDir.isDirectory()) { // Store the file in the outFileOrDir directory outputDir = outFileOrDir; // Use the suggested file name outputFile = getSuggestedFileName(); destinationFile = new File(outputDir, outputFile); } else { // If the user explicitly requested a file name and directory, proceed regularly destinationFile = outFileOrDir; } } // Set the user-selected destination path or file model.setTargetFile(destinationFile); } /** * Sign the document after all of the parameters have been loaded into the {@link SignatureCLIModel}. * Some of the code has been taken from {@link eu.europa.ec.markt.dss.applet.wizard.signature.SignatureWizardController#signDocument()}. * * @param model The signature model * @return The {@link java.io.FileOutputStream} of the signed document * * @throws it.latraccia.dss.util.exception.SignatureTargetFileException Thrown if there is an input/output * exception while/reading a file or a * network stream * * @throws java.security.NoSuchAlgorithmException Thrown if a specified algorithm isn't available */ private File signDocument(SignatureCLIModel model) throws SignatureTargetFileException, NoSuchAlgorithmException { final File fileToSign = model.getSelectedFile(); final SignatureTokenConnection tokenConnection = model.getTokenConnection(); final DSSPrivateKeyEntry privateKey = model.getSelectedPrivateKey(); final SignatureParameters parameters = new SignatureParameters(); parameters.setSigningDate(new Date()); parameters.setPrivateKeyEntry(privateKey); parameters.setSignatureFormat(SignatureFormat.valueByName(model.getLevel())); parameters.setSignaturePackaging(model.getPackaging()); if (model.isClaimedCheck()) { parameters.setClaimedSignerRole(model.getClaimedRole()); } String moccaSignatureAlgorithm = model.getMoccaSignatureAlgorithm(); // If a MOCCA algorithm is selected, ensure it is SHA256 if ("SHA256".equalsIgnoreCase(moccaSignatureAlgorithm)) { parameters.setDigestAlgorithm(eu.europa.ec.markt.dss.DigestAlgorithm.SHA256); } else { // Set the custom digest algorithm parameters.setDigestAlgorithm(model.getDigestAlgorithm()); } if (model.isSignaturePolicyCheck()) { final byte[] hashValue = Base64.decodeBase64(model.getSignaturePolicyValue()); final SignatureParameters.Policy policy = parameters.getSignaturePolicy(); policy.setHashValue(hashValue); policy.setId(model.getSignaturePolicyId()); eu.europa.ec.markt.dss.DigestAlgorithm digestAlgo = eu.europa.ec.markt.dss.DigestAlgorithm .forName(model.getSignaturePolicyAlgo()); policy.setDigestAlgo(digestAlgo); } FileOutputStream output; try { output = new FileOutputStream(model.getTargetFile()); } catch (FileNotFoundException e) { throw new SignatureTargetFileException(e); } final TrustedListCertificateVerifier certificateVerifier = (TrustedListCertificateVerifier) model .getCertificateVerifier(model.getCRLSource(), model.getOSCPSource(), model.getCertificateSource(model.getCertificateSource102853())); DSSDocument signedDocument; try { signedDocument = SigningUtils.signDocument(fileToSign, parameters, model.getTSPSource(), certificateVerifier, tokenConnection, privateKey); IOUtils.copy(signedDocument.openStream(), output); output.close(); } catch (DSSException dssException) { throw new SignatureTargetFileException(dssException); } catch (IOException ioException) { throw new SignatureTargetFileException(ioException); } System.out.println("SUCCESS."); System.out.println(String.format("Signed file: %s", model.getTargetFile().getAbsoluteFile())); return fileToSign; } } }