/* DigiDoc4J library * * This software is released under either the GNU Library General Public * License (see LICENSE.LGPL). * * Note that the only valid version of the LGPL license as far as this * project is concerned is the original GNU Library General Public License * Version 2.1, February 1999 */ package org.digidoc4j; import static java.util.Arrays.asList; import static org.digidoc4j.ContainerBuilder.BDOC_CONTAINER_TYPE; import static org.digidoc4j.ContainerBuilder.DDOC_CONTAINER_TYPE; import java.io.Serializable; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.digidoc4j.exceptions.ContainerWithoutFilesException; import org.digidoc4j.exceptions.NotSupportedException; import org.digidoc4j.exceptions.SignatureTokenMissingException; import org.digidoc4j.exceptions.SignerCertificateRequiredException; import org.digidoc4j.exceptions.TechnicalException; import org.digidoc4j.impl.bdoc.BDocSignatureBuilder; import org.digidoc4j.impl.ddoc.DDocSignatureBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>Creating signatures on a container.</p> * <p>Here's an example of creating a signature:</p> * <p><code> * {@link Signature} signature = {@link SignatureBuilder}. <br/> *    {@link SignatureBuilder#aSignature(Container) aSignature(container)}. <br/> *    {@link SignatureBuilder#withCity(String) withCity("San Pedro")}. <br/> *    {@link SignatureBuilder#withCountry(String) withCountry("Val Verde")}. <br/> *    {@link SignatureBuilder#withRoles(String...) withRoles("Manager", "Suspicious Fisherman")}. <br/> *    {@link SignatureBuilder#withSignatureDigestAlgorithm(DigestAlgorithm) withSignatureDigestAlgorithm(DigestAlgorithm.SHA256)}. // Digest algorithm is SHA-256 <br/> *    {@link SignatureBuilder#withSignatureProfile(SignatureProfile) withSignatureProfile(SignatureProfile.LT_TM)}. // Signature profile is Time-Mark <br/> *    {@link SignatureBuilder#withSignatureToken(SignatureToken) withSignatureToken(signatureToken)}. // Use signature token <br/> *    {@link SignatureBuilder#invokeSigning() invokeSigning()}; // Creates a signature using signature token * </code></p> * <p> * Use {@link SignatureBuilder#aSignature(Container) SignatureBuilder.aSignature(container)} to create a new signature builder, * populate the builder with data and then call {@link SignatureBuilder#invokeSigning()} to create a signature on the container * using {@link SignatureToken}. Signature token must be provided with {@link SignatureBuilder#withSignatureToken(SignatureToken)}. * </p> * <p> * Use {@link SignatureBuilder#buildDataToSign()} to create {@link DataToSign} object * that can be used in external signing (e.g. signing in the Web). To build {@link DataToSign} object, signer certificate * must be provided with {@link SignatureBuilder#withSigningCertificate(X509Certificate)}. * </p> */ public abstract class SignatureBuilder implements Serializable { private final static Logger logger = LoggerFactory.getLogger(SignatureBuilder.class); protected SignatureParameters signatureParameters = new SignatureParameters(); protected SignatureToken signatureToken; protected Container container; protected static Map<String, Class<? extends SignatureBuilder>> customSignatureBuilders = new HashMap<>(); /** * Create a new signature builder based on a container. * Container is used to determine which type of signature should be created. * * @param container container to be signed. * @return builder for creating a signature. */ public static SignatureBuilder aSignature(Container container) { SignatureBuilder builder = createBuilder(container); builder.setContainer(container); return builder; } private static SignatureBuilder createBuilder(Container container) { String containerType = container.getType(); if(isCustomContainerType(containerType)) { return createCustomSignatureBuilder(containerType); } else if (isContainerType(containerType, BDOC_CONTAINER_TYPE)) { return new BDocSignatureBuilder(); } else if (isContainerType(containerType, DDOC_CONTAINER_TYPE)) { return new DDocSignatureBuilder(); } else { logger.error("Unknown container type: " + container.getType()); throw new NotSupportedException("Unknown container type: " + container.getType()); } } /** * Invokes a signing process on the container with a signature token (See {@link SignatureToken}). * Signature token must be provided with {@link SignatureBuilder#withSignatureToken}. * * @return a new signature on the container. * @throws SignatureTokenMissingException if signature token is not provided with {@link SignatureBuilder#withSignatureToken} * @see SignatureToken */ public Signature invokeSigning() throws SignatureTokenMissingException { if (signatureToken == null) { logger.error("Cannot invoke signing without signature token. Add 'withSignatureToken()' method call or call 'buildDataToSign() instead.'"); throw new SignatureTokenMissingException(); } return invokeSigningProcess(); } /** * Signing process implementation that is called by {@link SignatureBuilder#invokeSigning()} method. * Must be implemented by the class implementing the builder. * * @return a new signature on the container. */ protected abstract Signature invokeSigningProcess(); /** * Creates data to be signed externally. * * If the signing process involves signing the container externally (e.g. signing in the Web by a browser plugin), * then {@link DataToSign} provides necessary data for creating a signature externally. * * @return data to be signed externally. * @throws SignerCertificateRequiredException signer certificate must be provided using {@link SignatureBuilder#withSigningCertificate(X509Certificate)} * @throws ContainerWithoutFilesException container must have at least one data file to be signed. Signature cannot be given on an empty container. */ public abstract DataToSign buildDataToSign() throws SignerCertificateRequiredException, ContainerWithoutFilesException; /** * Open signature from an existing signature document (XAdES, PAdES, CAdES etc.) * * The signature document must be complete, containing all the necessary data (e.g. Signer's certificate, * OCSP responses, Timestamps, signature values etc). An example would be a signature document in XAdES format which * is an XML document transformed into a byte array. * * @param signatureDocument complete signature document in bytes. * @return a signature object representing the signatureDocument. */ public abstract Signature openAdESSignature(byte[] signatureDocument); /** * Setting custom signature builder implementation used when creating signatures for the particular container type. * * @param containerType container type corresponding to the signature builder. * @param signatureBuilderClass signature builder class used for creating signatures for the container type. * @param <T> signature builder class extending {@link SignatureBuilder}. */ public static <T extends SignatureBuilder> void setSignatureBuilderForContainerType(String containerType, Class<T> signatureBuilderClass) { customSignatureBuilders.put(containerType, signatureBuilderClass); } /** * Clears all custom signature builders to use only default signature builders. */ public static void removeCustomSignatureBuilders() { customSignatureBuilders.clear(); } /** * Set a city to the signature production place. * * @param cityName city to use on the signature production place. * @return builder for creating a signature */ public SignatureBuilder withCity(String cityName) { signatureParameters.setCity(cityName); return this; } /** * Set a state or province to the signature production place. * * @param stateOrProvince name of the state or province on the signature production place. * @return builder for creating a signature */ public SignatureBuilder withStateOrProvince(String stateOrProvince) { signatureParameters.setStateOrProvince(stateOrProvince); return this; } /** * Set a postal code to the signature production place. * * @param postalCode postal code on the signature production place. * @return builder for creating a signature. */ public SignatureBuilder withPostalCode(String postalCode) { signatureParameters.setPostalCode(postalCode); return this; } /** * Set a country name to the signature production place. * * @param country name of the country on the signature production place. * @return builder for creating a signature. */ public SignatureBuilder withCountry(String country) { signatureParameters.setCountry(country); return this; } /** * Set roles to the signer. * * @param roles list of roles of a signer. * @return builder for creating a signature. */ public SignatureBuilder withRoles(String... roles) { if (signatureParameters.getRoles() == null) { signatureParameters.setRoles(asList(roles)); } else { signatureParameters.getRoles().addAll(asList(roles)); } return this; } /** * Set signature digest algorithm used to generate a signature. * * @param digestAlgorithm signature digest algorithm. * @return builder for creating a signature. */ public SignatureBuilder withSignatureDigestAlgorithm(DigestAlgorithm digestAlgorithm) { signatureParameters.setDigestAlgorithm(digestAlgorithm); return this; } /** * Set a signature profile: Time Mark, Time Stamp, Archive Time Stamp or no profile. Default is Time Stamp. * * @param signatureProfile signature profile. * @return builder for creating a signature. */ public SignatureBuilder withSignatureProfile(SignatureProfile signatureProfile) { signatureParameters.setSignatureProfile(signatureProfile); return this; } /** * Set a signing certificate to be used when creating data to be signed. * * @param certificate X509 signer's certificate. * @return builder for creating a signature. */ public SignatureBuilder withSigningCertificate(X509Certificate certificate) { signatureParameters.setSigningCertificate(certificate); return this; } /** * Set signature ID. * * @param signatureId signature id. * @return builder for creating a signature. */ public SignatureBuilder withSignatureId(String signatureId) { signatureParameters.setSignatureId(signatureId); return this; } /** * Set signature token to be used in the signing process. * * @param signatureToken signature token. * @return builder for creating a signature. */ public SignatureBuilder withSignatureToken(SignatureToken signatureToken) { this.signatureToken = signatureToken; return this; } public SignatureBuilder withEncryptionAlgorithm(EncryptionAlgorithm encryptionAlgorithm) { signatureParameters.setEncryptionAlgorithm(encryptionAlgorithm); return this; } protected void setContainer(Container container) { this.container = container; } private static boolean isCustomContainerType(String containerType) { return customSignatureBuilders.containsKey(containerType); } private static boolean isContainerType(String containerType, String ddocContainerType) { return StringUtils.equalsIgnoreCase(ddocContainerType, containerType); } private static SignatureBuilder createCustomSignatureBuilder(String containerType) { Class<? extends SignatureBuilder> builderClass = customSignatureBuilders.get(containerType); try { logger.debug("Instantiating signature builder class " + builderClass.getName() + " for container type " + containerType); return builderClass.newInstance(); } catch (ReflectiveOperationException e) { logger.error("Unable to instantiate custom signature builder class " + builderClass.getName() + " for type " + containerType); throw new TechnicalException("Unable to instantiate custom signature builder class " + builderClass.getName() + " for type " + containerType, e); } } }