org.digidoc4j.impl.BDocContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.digidoc4j.impl.BDocContainer.java

Source

/* 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.impl;

import eu.europa.ec.markt.dss.DSSXMLUtils;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.parameter.BLevelParameters;
import eu.europa.ec.markt.dss.signature.*;
import eu.europa.ec.markt.dss.signature.asic.ASiCService;
import eu.europa.ec.markt.dss.validation102853.AdvancedSignature;
import eu.europa.ec.markt.dss.validation102853.SignaturePolicy;
import eu.europa.ec.markt.dss.validation102853.SignedDocumentValidator;
import eu.europa.ec.markt.dss.validation102853.asic.ASiCContainerValidator;
import eu.europa.ec.markt.dss.validation102853.asic.ASiCXMLDocumentValidator;
import eu.europa.ec.markt.dss.validation102853.ocsp.BDocTMOcspSource;
import eu.europa.ec.markt.dss.validation102853.ocsp.BDocTSOcspSource;
import eu.europa.ec.markt.dss.validation102853.ocsp.SKOnlineOCSPSource;
import eu.europa.ec.markt.dss.validation102853.report.Conclusion;
import eu.europa.ec.markt.dss.validation102853.report.Reports;
import eu.europa.ec.markt.dss.validation102853.report.SimpleReport;
import eu.europa.ec.markt.dss.validation102853.tsl.TrustedListsCertificateSource;
import eu.europa.ec.markt.dss.validation102853.tsp.OnlineTSPSource;
import eu.europa.ec.markt.dss.validation102853.xades.XAdESSignature;
import eu.europa.ec.markt.dss.validation102853.xades.XPathQueryHolder;
import org.apache.commons.io.IOUtils;
import org.digidoc4j.*;
import org.digidoc4j.exceptions.*;
import org.digidoc4j.utils.Helper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static eu.europa.ec.markt.dss.DigestAlgorithm.SHA256;
import static eu.europa.ec.markt.dss.DigestAlgorithm.forName;
import static eu.europa.ec.markt.dss.parameter.BLevelParameters.SignerLocation;
import static eu.europa.ec.markt.dss.signature.SignatureLevel.*;
import static eu.europa.ec.markt.dss.signature.SignaturePackaging.DETACHED;
import static eu.europa.ec.markt.dss.validation102853.SignatureForm.XAdES;
import static java.util.Arrays.asList;
import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.digidoc4j.Container.SignatureProfile.LT;

/**
 * BDOC container implementation
 */
public class BDocContainer extends Container {
    private static final String TM_POLICY = "urn:oid:1.3.6.1.4.1.10015.1000.3.2.1";
    private static final String OIDAS_URN = "OIDAsURN";
    private static final String XADES_SIGNED_PROPERTIES = "http://uri.etsi.org/01903#SignedProperties";
    public static final int ONE_MB_IN_BYTES = 1048576;

    final Logger logger = LoggerFactory.getLogger(BDocContainer.class);

    private final Map<String, DataFile> dataFiles = new HashMap<>();
    private Map<String, List<DigiDoc4JException>> additionalVerificationErrors = new HashMap<>();
    private SKCommonCertificateVerifier commonCertificateVerifier;
    protected DocumentSignatureService asicService;
    private eu.europa.ec.markt.dss.parameter.SignatureParameters dssSignatureParameters;
    private SignatureParameters signatureParameters = new SignatureParameters();
    protected DSSDocument signedDocument;
    private List<Signature> signatures = new ArrayList<>();
    Configuration configuration = null;
    private static final MimeType BDOC_MIME_TYPE = MimeType.ASICE;
    private transient Reports validationReport;
    private boolean isTimeMark = false;

    /**
     * Create a new container object of type BDOC.
     */
    public BDocContainer() {
        logger.debug("");
        this.configuration = new Configuration();
        initASiC();

        logger.debug("New BDoc container created");
    }

    @Override
    public SignedInfo prepareSigning(X509Certificate signerCert) {
        logger.debug("");
        String signatureId = signatureParameters.getSignatureId();
        byte[] signedInfo = getDataToSign(signatureId != null ? signatureId : "S" + getSignatures().size(),
                signerCert);

        return new SignedInfo(signedInfo, signatureParameters.getDigestAlgorithm());
    }

    @Override
    public String getSignatureProfile() {
        logger.debug("");
        return dssSignatureParameters.getSignatureLevel().name();
    }

    /**
     * Create a new container object of type BDOC with given configuration.
     * Configuration is immutable. You cant change already set configuration.
     *
     * @param configuration sets container configuration
     */
    public BDocContainer(Configuration configuration) {
        logger.debug("");
        initASiC();
        configuration.getTSL();
        this.configuration = configuration.copy();
        logger.debug("New BDoc container created");
    }

    @Override
    public void setSignatureParameters(SignatureParameters signatureParameters) {
        logger.debug("");
        this.signatureParameters = signatureParameters.copy();

        setDigestAlgorithm();

        setEncryptionAlgorithm();
        addSignerInformation();
    }

    private void setEncryptionAlgorithm() {
        logger.debug("");
        if (signatureParameters.getEncryptionAlgorithm() == EncryptionAlgorithm.ECDSA) {
            dssSignatureParameters.setEncryptionAlgorithm(eu.europa.ec.markt.dss.EncryptionAlgorithm.ECDSA);
        }
    }

    private void setDigestAlgorithm() {
        logger.debug("");
        if (signatureParameters.getDigestAlgorithm() == null) {
            signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256);
        }
        dssSignatureParameters.setDigestAlgorithm(forName(signatureParameters.getDigestAlgorithm().name(), SHA256));
    }

    @Override
    public DigestAlgorithm getDigestAlgorithm() {
        DigestAlgorithm digestAlgorithm = signatureParameters.getDigestAlgorithm();
        logger.debug("");
        return digestAlgorithm;
    }

    private void initASiC() {
        logger.debug("");
        dssSignatureParameters = new eu.europa.ec.markt.dss.parameter.SignatureParameters();
        dssSignatureParameters.setSignatureLevel(ASiC_E_BASELINE_LT);
        dssSignatureParameters.setSignaturePackaging(DETACHED);
        dssSignatureParameters.setDigestAlgorithm(SHA256);
        signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256);
        dssSignatureParameters.aSiC().setUnderlyingForm(XAdES);
        dssSignatureParameters.aSiC().setZipComment(Helper.createUserAgent(this));
        dssSignatureParameters.bLevel()
                .setSigningCertificateDigestMethod(eu.europa.ec.markt.dss.DigestAlgorithm.SHA256);
        //dssSignatureParameters.setSignedInfoCanonicalizationMethod(Canonicalizer.ALGO_ID_C14N11_OMIT_COMMENTS);

        commonCertificateVerifier = new SKCommonCertificateVerifier();
        commonCertificateVerifier.setCrlSource(null);
        asicService = new ASiCService(commonCertificateVerifier);
    }

    private void addSignaturePolicy() {
        logger.debug("");
        BLevelParameters.Policy signaturePolicy = new BLevelParameters.Policy();
        signaturePolicy.setId("urn:oid:1.3.6.1.4.1.10015.1000.3.2.1");
        signaturePolicy.setDigestValue(decodeBase64("3Tl1oILSvOAWomdI9VeWV6IA/32eSXRUri9kPEz1IVs="));
        signaturePolicy.setDigestAlgorithm(SHA256);
        try {
            URI uri = new URI("https://www.sk.ee/repository/bdoc-spec21.pdf");
            List<URI> qualifiers = asList(uri);
            signaturePolicy.setSigPolicyQualifiers(qualifiers);
        } catch (URISyntaxException ignore) {
        }
        dssSignatureParameters.bLevel().setSignaturePolicy(signaturePolicy);
    }

    /**
     * Opens the container from a file.
     *
     * @param path container file name with path
     */
    public BDocContainer(String path) {
        this(path, new Configuration());
    }

    /**
     * Opens container from a stream
     *
     * @param stream                      stream to read container from
     * @param actAsBigFilesSupportEnabled acts as configuration parameter
     * @see org.digidoc4j.Configuration#isBigFilesSupportEnabled() returns true
     */
    public BDocContainer(InputStream stream, boolean actAsBigFilesSupportEnabled) {
        logger.debug("");
        this.configuration = new Configuration();
        initASiC();
        try {
            if (actAsBigFilesSupportEnabled) {
                signedDocument = new StreamDocument(stream, null, BDOC_MIME_TYPE);
            } else
                signedDocument = new InMemoryDocument(IOUtils.toByteArray(stream), null, BDOC_MIME_TYPE);
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new DigiDoc4JException(e);
        }

        readsOpenedDocumentDetails();
    }

    /**
     * Opens container from a file with specified configuration settings
     *
     * @param path          container file name with path
     * @param configuration configuration settings
     */
    public BDocContainer(String path, Configuration configuration) {
        logger.debug("Opens file: " + path);
        initASiC();

        try {
            signedDocument = new FileDocument(path);
            signedDocument.setMimeType(MimeType.ASICE);
            checkMimeType(path);
        } catch (DSSException e) {
            logger.error(e.getMessage());
            throw new DigiDoc4JException(e);
        }

        configuration.getTSL();
        this.configuration = configuration.copy();
        readsOpenedDocumentDetails();
    }

    private void checkMimeType(String path) {
        logger.debug("Check mime type for " + path);
        String bdocMimeTypeFromZip = getBdocMimeTypeFromZip(path).trim();
        try {
            if (!MimeType.ASICE.equals(MimeType.fromMimeTypeString(bdocMimeTypeFromZip))) {
                throw new UnsupportedFormatException(bdocMimeTypeFromZip);
            }
        } catch (DSSException e) {
            logger.error("Unsupported format: " + bdocMimeTypeFromZip);
            throw new UnsupportedFormatException(bdocMimeTypeFromZip);
        }
    }

    private String getBdocMimeTypeFromZip(String path) {
        logger.debug("Get mime type from zip for " + path);
        String mimeType;
        try {
            ZipFile zipFile = new ZipFile(path);
            ZipEntry entry = zipFile.getEntry("mimetype");
            if (entry == null) {
                logger.error("Unsupported format, mimetype missing");
                throw new UnsupportedFormatException("Not an asic-e document. Mimetype is missing.");
            }
            InputStream stream = zipFile.getInputStream(entry);
            mimeType = IOUtils.toString(stream);
            stream.close();
            zipFile.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new DigiDoc4JException(e);
        }

        logger.debug("Mime type " + mimeType);
        return mimeType;
    }

    private void readsOpenedDocumentDetails() {
        logger.debug("");

        SignedDocumentValidator validator = ASiCContainerValidator.fromDocument(signedDocument);

        loadSignatures(validator);
        loadAttachments(validator);

        //TODO must be changed when extending signature is possible in sd-dss currently is possible to extend whole
        //container and it extend also all signatures
        setSignatureProfile(getSignatures().size() != 0 ? getSignature(0).getProfile() : LT);
        eu.europa.ec.markt.dss.DigestAlgorithm digestAlgorithm = dssSignatureParameters.getDigestAlgorithm();
        if (digestAlgorithm != null) {
            signatureParameters.setDigestAlgorithm(DigestAlgorithm.valueOf(digestAlgorithm.getName()));
        } else {
            signatureParameters.setDigestAlgorithm(null);
        }

        logger.debug("New BDoc container created");
    }

    private void loadAttachments(SignedDocumentValidator validator) {
        logger.debug("");
        for (DSSDocument externalContent : validator.getDetachedContents()) {
            if (!"mimetype".equals(externalContent.getName())
                    && !"META-INF/manifest.xml".equals(externalContent.getName())) {
                dataFiles.put(externalContent.getName(), new DataFile(externalContent.getBytes(),
                        externalContent.getName(), externalContent.getMimeType().getMimeTypeString()));
            }
        }
    }

    private Map<String, SimpleReport> loadValidationResults(SignedDocumentValidator validator) {
        logger.debug("");
        Map<String, SimpleReport> simpleReports = new HashMap<>();

        Reports report = validate(validator);

        dssSignatureParameters.setDigestAlgorithm(report.getDiagnosticData().getSignatureDigestAlgorithm());

        do {
            SimpleReport simpleReport = report.getSimpleReport();
            if (simpleReport.getSignatureIdList().size() > 0)
                simpleReports.put(simpleReport.getSignatureIdList().get(0), simpleReport);
            report = report.getNextReports();
        } while (report != null);
        return simpleReports;
    }

    private void loadSignatures(SignedDocumentValidator validator) {
        logger.debug("");
        Map<String, SimpleReport> simpleReports = loadValidationResults(validator);
        List<AdvancedSignature> signatureList = validator.getSignatures();

        additionalVerificationErrors = new HashMap<>();
        for (AdvancedSignature advancedSignature : signatureList) {
            List<DigiDoc4JException> validationErrors = new ArrayList<>();
            String reportSignatureId = advancedSignature.getId();
            additionalVerificationErrors.put(reportSignatureId, validatePolicy(advancedSignature));
            DigiDoc4JException referenceError = validateSignedPropertiesReference(advancedSignature);
            if (referenceError != null)
                additionalVerificationErrors.get(reportSignatureId).add(referenceError);
            SimpleReport simpleReport = getSimpleReport(simpleReports, reportSignatureId);
            if (simpleReport != null) {
                for (Conclusion.BasicInfo error : simpleReport.getErrors(reportSignatureId)) {
                    String errorMessage = error.toString();
                    logger.info(errorMessage);
                    validationErrors.add(new DigiDoc4JException(errorMessage));
                }
            }
            validationErrors.addAll(additionalVerificationErrors.get(reportSignatureId));
            signatures.add(new BDocSignature((XAdESSignature) advancedSignature, validationErrors));
        }
    }

    private DigiDoc4JException validateSignedPropertiesReference(AdvancedSignature advancedSignature) {
        logger.debug("");
        List<Element> signatureReferences = ((XAdESSignature) advancedSignature).getSignatureReferences();
        int nrOfSignedPropertiesReferences = 0;
        for (Element signatureReference : signatureReferences) {
            if (XADES_SIGNED_PROPERTIES.equals(signatureReference.getAttribute("Type")))
                nrOfSignedPropertiesReferences++;
        }
        if (nrOfSignedPropertiesReferences == 1)
            return null;
        String errorMessage;
        errorMessage = nrOfSignedPropertiesReferences == 0 ? "Signed properties missing"
                : "Multiple signed properties";
        logger.info(errorMessage);
        return (new DigiDoc4JException(errorMessage));
    }

    private List<DigiDoc4JException> validatePolicy(AdvancedSignature advancedSignature) {
        logger.debug("");
        ArrayList<DigiDoc4JException> validationErrors = new ArrayList<>();
        SignaturePolicy policy = advancedSignature.getPolicyId();
        if (policy != null) {
            String policyIdentifier = policy.getIdentifier().trim();
            if (!TM_POLICY.equals(policyIdentifier)) {
                validationErrors.add(new DigiDoc4JException("Wrong policy identifier: " + policyIdentifier));
                return validationErrors;
            }
            if (isBlank(policy.getUrl()))
                validationErrors
                        .add(new DigiDoc4JException("Policy url is missing for identifier: " + policyIdentifier));

            XPathQueryHolder xPathQueryHolder = ((XAdESSignature) advancedSignature).getXPathQueryHolder();
            Element signatureElement = ((XAdESSignature) advancedSignature).getSignatureElement();
            Element element = DSSXMLUtils.getElement(signatureElement,
                    xPathQueryHolder.XPATH_SIGNATURE_POLICY_IDENTIFIER);
            Element identifier = DSSXMLUtils.getElement(element,
                    "./xades:SignaturePolicyId/xades:SigPolicyId/xades:Identifier");
            if (!OIDAS_URN.equals(identifier.getAttribute("Qualifier"))) {
                validationErrors.add(new DigiDoc4JException(
                        "Wrong policy identifier qualifier: " + identifier.getAttribute("Qualifier")));
            }
        }

        return validationErrors;
    }

    private SimpleReport getSimpleReport(Map<String, SimpleReport> simpleReports, String fromSignatureId) {
        logger.debug("signature id : " + fromSignatureId);
        SimpleReport simpleReport = simpleReports.get(fromSignatureId);
        if (simpleReport != null && simpleReports.size() == 1) {
            return simpleReports.values().iterator().next();
        }
        return simpleReport;
    }

    /**
     * Load configuration settings
     *
     * @param fileName file containing configuration settings
     */
    public void loadConfiguration(String fileName) {
        logger.debug("");
        configuration.loadConfiguration(fileName);
    }

    @Override
    public void addDataFile(String path, String mimeType) {
        logger.debug("Path: " + path + ", mime type: " + mimeType);

        verifyIfAllowedToAddDataFile(path);

        validationReport = null;
        try {
            long cachedFileSizeInBytes = configuration.getMaxDataFileCachedInBytes();
            if (configuration.isBigFilesSupportEnabled() && new File(path).length() > cachedFileSizeInBytes) {
                dataFiles.put(path, new DataFile(path, mimeType));
            } else {
                InputStream is = Files.newInputStream(Paths.get(path));
                dataFiles.put(path, new DataFile(IOUtils.toByteArray(is), path, mimeType));
                is.close();
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new DigiDoc4JException(e);
        }
    }

    private void verifyIfAllowedToAddDataFile(String path) {
        if (signatures.size() > 0) {
            String errorMessage = "Datafiles cannot be added to an already signed container";
            logger.error(errorMessage);
            throw new DigiDoc4JException(errorMessage);
        }

        checkForDuplicateDataFile(path);
    }

    private void checkForDuplicateDataFile(String path) {
        logger.debug("");
        String fileName = new File(path).getName();
        for (String key : dataFiles.keySet()) {
            if (dataFiles.get(key).getName().equals(fileName)) {
                String errorMessage = "Data file " + fileName + " already exists";
                logger.error(errorMessage);
                throw new DigiDoc4JException(errorMessage);
            }
        }
    }

    @Override
    public void addDataFile(InputStream is, String fileName, String mimeType) {
        logger.debug("File name: " + fileName + ", mime type: " + mimeType);

        verifyIfAllowedToAddDataFile(fileName);

        validationReport = null;
        try {
            if (configuration.isBigFilesSupportEnabled()) {
                dataFiles.put(fileName, new DataFile(is, fileName, mimeType));
            } else {
                dataFiles.put(fileName, new DataFile(IOUtils.toByteArray(is), fileName, mimeType));
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new DigiDoc4JException(e);
        }
    }

    @Override
    public void addRawSignature(byte[] signature) {
        logger.debug("");
        validationReport = null;
        InputStream signatureStream = getByteArrayInputStream(signature);
        addRawSignature(signatureStream);
        IOUtils.closeQuietly(signatureStream);
    }

    InputStream getByteArrayInputStream(byte[] signature) {
        logger.debug("");
        return new ByteArrayInputStream(signature);
    }

    @Override //TODO NotYetImplementedException
    public void addRawSignature(InputStream signatureStream) {
        logger.warn("Not yet implemented");
        throw new NotYetImplementedException();
    }

    @Override
    public List<DataFile> getDataFiles() {
        logger.debug("");
        return new ArrayList<>(dataFiles.values());
    }

    @Override
    public DataFile getDataFile(int index) {
        logger.debug("get data file with index: " + index);
        return getDataFiles().get(index);
    }

    @Override
    public void removeDataFile(String fileName) {
        logger.debug("File name: " + fileName);

        if (signatures.size() > 0) {
            String errorMessage = "Datafiles cannot be removed from an already signed container";
            logger.error(errorMessage);
            throw new DigiDoc4JException(errorMessage);
        }

        if (dataFiles.remove(fileName) == null) {
            DigiDoc4JException exception = new DigiDoc4JException("File not found");
            logger.error(exception.getMessage());
            throw exception;
        }
    }

    @Override
    public void removeSignature(int index) {
        logger.debug("Index: " + index);

        SignedDocumentValidator validator = ASiCXMLDocumentValidator.fromDocument(signedDocument);
        DSSDocument signingDocument = getAttachment();
        DSSDocument signature = validator.removeSignature("S" + index);
        dssSignatureParameters.setDetachedContent(signingDocument);

        signedDocument = null;
        do {
            dssSignatureParameters.aSiC().setSignatureFileName(getSignatureFileName(signature));
            signedDocument = ((ASiCService) asicService).buildASiCContainer(signingDocument, signedDocument,
                    dssSignatureParameters, createBareDocument(signature));
            signature = signature.getNextDocument();
        } while (signature != null);

        validationReport = null;
        signatures.remove(index);
    }

    private DSSDocument createBareDocument(DSSDocument signature) {
        logger.debug("");
        if (signature.getName() == null)
            return signature;
        Document root = DSSXMLUtils.buildDOM(signature);
        final Element signatureEl = (Element) root.getDocumentElement().getFirstChild();
        return new InMemoryDocument(DSSXMLUtils.serializeNode(signatureEl));
    }

    private String getSignatureFileName(DSSDocument signature) {
        logger.debug("");
        if (signature.getName() == null)
            return "signatures0.xml";
        return signature.getName().substring(signature.getName().lastIndexOf('/') + 1);
    }

    @Override
    public void save(String path) {
        logger.debug("Path: " + path);
        documentMustBeInitializedCheck();
        signedDocument.save(path);
    }

    @Override
    public void save(OutputStream out) {
        logger.debug("");
        try {
            IOUtils.copyLarge(signedDocument.openStream(), out);
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new DigiDoc4JException(e);
        }
    }

    //TODO NotYetImplementedException
    private void documentMustBeInitializedCheck() {
        logger.debug("");
        if (signedDocument == null) {
            logger.warn("Not yet implemented");
            throw new NotYetImplementedException();
        }
    }

    @Override
    public Signature sign(Signer signer) {
        logger.debug("");
        byte[] dataToSign;
        String signatureId = signatureParameters.getSignatureId();
        dataToSign = getDataToSign(signatureId != null ? signatureId : "S" + getSignatures().size(),
                signer.getCertificate());

        byte[] signature = signer.sign(this, dataToSign);
        validationReport = null;
        return signRaw(signature);
    }

    private byte[] getDataToSign(String setSignatureId, X509Certificate signerCertificate) {
        logger.debug("");
        if (isTimeMark)
            addSignaturePolicy();

        dssSignatureParameters.clearCertificateChain();
        dssSignatureParameters.setDeterministicId(setSignatureId);
        dssSignatureParameters.aSiC().setSignatureFileName("signatures" + signatures.size() + ".xml");
        dssSignatureParameters.setSigningCertificate(signerCertificate);

        DSSDocument attachment = getAttachment();
        dssSignatureParameters.setDetachedContent(attachment);

        return asicService.getDataToSign(attachment, dssSignatureParameters);
    }

    @Override
    public Signature signRaw(byte[] rawSignature) {
        logger.debug("");

        SKOnlineOCSPSource ocspSource = getOcspSource(rawSignature);
        commonCertificateVerifier.setTrustedCertSource(configuration.getTSL());
        String userAgent = Helper.createUserAgent(this);
        ocspSource.setUserAgent(userAgent);

        commonCertificateVerifier.setOcspSource(ocspSource);
        OnlineTSPSource tspSource = new OnlineTSPSource(getConfiguration().getTspSource());
        tspSource.setUserAgent(userAgent);
        asicService.setTspSource(tspSource);

        String deterministicId = getDssSignatureParameters().getDeterministicId();

        try {
            signedDocument = asicService.signDocument(getSigningDocument(), dssSignatureParameters, rawSignature);
        } catch (DSSException e) {
            logger.error(e.getMessage());
            if ("OCSP request failed".equals(e.getMessage()))
                throw new OCSPRequestFailedException();
            throw new DigiDoc4JException(e);
        }

        XAdESSignature xAdESSignature = getSignatureById(deterministicId);

        validationReport = null;
        Signature signature = new BDocSignature(xAdESSignature);
        signatures.add(signature);

        return signature;
    }

    private SKOnlineOCSPSource getOcspSource(byte[] signatureValue) {
        logger.debug("");
        if (isTimeMark && signatureValue != null)
            return new BDocTMOcspSource(configuration, signatureValue);
        return new BDocTSOcspSource(configuration);
    }

    private DSSDocument getSigningDocument() {
        logger.debug("");
        if (signedDocument == null) {
            signedDocument = getAttachment();
        }
        return signedDocument;
    }

    private DSSDocument getAttachment() {
        logger.debug("");
        DSSDocument attachment;

        if (dataFiles.size() == 0) {
            String errorMessage = "Container does not contain any attachments";
            logger.error(errorMessage);
            throw new DigiDoc4JException(errorMessage);
        }
        Iterator<String> iterator = dataFiles.keySet().iterator();
        attachment = getDssDocumentFromDataFile(dataFiles.get(iterator.next()));
        while (iterator.hasNext()) {
            String fileName = iterator.next();
            attachment.setNextDocument(getDssDocumentFromDataFile(dataFiles.get(fileName)));
        }

        return attachment;
    }

    private DSSDocument getDssDocumentFromDataFile(DataFile dataFile) {
        logger.debug("");
        DSSDocument attachment;
        MimeType mimeType = MimeType.fromMimeTypeString(dataFile.getMediaType());
        long cachedFileSizeInMB = configuration.getMaxDataFileCachedInMB();
        if (configuration.isBigFilesSupportEnabled()
                && dataFile.getFileSize() > cachedFileSizeInMB * ONE_MB_IN_BYTES) {
            attachment = new StreamDocument(dataFile.getStream(), dataFile.getName(), mimeType);
        } else {
            attachment = new InMemoryDocument(dataFile.getBytes(), dataFile.getName(), mimeType);
        }
        return attachment;
    }

    private XAdESSignature getSignatureById(String deterministicId) {
        logger.debug("Id: " + deterministicId);
        SignedDocumentValidator validator = ASiCXMLDocumentValidator.fromDocument(signedDocument);
        validate(validator);
        List<AdvancedSignature> signatureList = validator.getSignatures();
        for (AdvancedSignature advancedSignature : signatureList) {
            if (advancedSignature.getId().equals(deterministicId)) {
                logger.debug("Signature found");
                return (XAdESSignature) advancedSignature;
            }
        }
        SignatureNotFoundException exception = new SignatureNotFoundException();
        logger.info(exception.getMessage());
        throw exception;
    }

    private Configuration getConfiguration() {
        logger.debug("");
        return configuration;
    }

    private void addSignerInformation() {
        logger.debug("");
        SignatureProductionPlace signatureProductionPlace = signatureParameters.getProductionPlace();
        List<String> signerRoles = signatureParameters.getRoles();

        BLevelParameters bLevelParameters = dssSignatureParameters.bLevel();

        if (!(isEmpty(signatureProductionPlace.getCity()) && isEmpty(signatureProductionPlace.getStateOrProvince())
                && isEmpty(signatureProductionPlace.getPostalCode())
                && isEmpty(signatureProductionPlace.getCountry()))) {

            SignerLocation signerLocation = new SignerLocation();

            if (!isEmpty(signatureProductionPlace.getCity()))
                signerLocation.setCity(signatureProductionPlace.getCity());
            if (!isEmpty(signatureProductionPlace.getStateOrProvince()))
                signerLocation.setStateOrProvince(signatureProductionPlace.getStateOrProvince());
            if (!isEmpty(signatureProductionPlace.getPostalCode()))
                signerLocation.setPostalCode(signatureProductionPlace.getPostalCode());
            if (!isEmpty(signatureProductionPlace.getCountry()))
                signerLocation.setCountry(signatureProductionPlace.getCountry());
            bLevelParameters.setSignerLocation(signerLocation);
        }
        for (String signerRole : signerRoles) {
            bLevelParameters.addClaimedSignerRole(signerRole);
        }
    }

    /**
     * Verify BDoc Container.
     *
     * @return result of the verification
     */
    public ValidationResult verify() {
        logger.debug("");
        documentMustBeInitializedCheck();

        SignedDocumentValidator validator = ASiCXMLDocumentValidator.fromDocument(signedDocument);

        Reports report = validate(validator);

        if ((signatures.size() > 0) && (((BDocSignature) signatures.get(0)).getOrigin().getReferences() == null)) {
            signatures = new ArrayList<>();
            loadSignatures(validator);
        }
        List<String> manifestErrors = new ManifestValidator(validator).validateDocument(signatures);
        return new ValidationResultForBDoc(report, signatures, manifestErrors, additionalVerificationErrors);
    }

    private Reports validate(SignedDocumentValidator validator) {
        logger.debug("Validator: " + validator);
        if (validationReport != null) {
            return validationReport;
        }

        commonCertificateVerifier.setOcspSource(null);

        TrustedListsCertificateSource trustedCertSource = configuration.getTSL();

        commonCertificateVerifier.setTrustedCertSource(trustedCertSource);
        validator.setCertificateVerifier(commonCertificateVerifier);

        try {
            validationReport = validator
                    .validateDocument(getValidationPolicyAsStream(getConfiguration().getValidationPolicy()));
            return validationReport;
        } catch (DSSException e) {
            logger.error(e.getMessage());
            throw new DigiDoc4JException(e);
        }
    }

    private InputStream getValidationPolicyAsStream(String policyFile) {
        logger.debug("");
        if (Files.exists(Paths.get(policyFile))) {
            try {
                return new FileInputStream(policyFile);
            } catch (FileNotFoundException ignore) {
                logger.warn(ignore.getMessage());
            }
        }

        return getClass().getClassLoader().getResourceAsStream(policyFile);
    }

    @Override
    public List<Signature> getSignatures() {
        logger.debug("");
        return signatures;
    }

    @Override
    public Signature getSignature(int index) {
        logger.debug("Get signature for index " + index);
        return getSignatures().get(index);
    }

    @Override
    public DocumentType getDocumentType() {
        logger.debug("");
        return DocumentType.BDOC;
    }

    @Override
    public ValidationResult validate() {
        logger.debug("");
        return verify();
    }

    private void extend(SignatureLevel signatureLevel) {
        logger.debug("");
        if (signatureLevel == dssSignatureParameters.getSignatureLevel()) {
            String errorMessage = "It is not possible to extend the signature to the same level";
            logger.error(errorMessage);
            throw new DigiDoc4JException(errorMessage);
        }
        SKOnlineOCSPSource ocspSource = getOcspSource(null);
        commonCertificateVerifier.setTrustedCertSource(configuration.getTSL());
        String userAgent = Helper.createUserAgent(this);
        ocspSource.setUserAgent(userAgent);
        commonCertificateVerifier.setOcspSource(ocspSource);
        OnlineTSPSource tspSource = new OnlineTSPSource(getConfiguration().getTspSource());
        tspSource.setUserAgent(userAgent);
        asicService.setTspSource(tspSource);

        dssSignatureParameters.setSignatureLevel(signatureLevel);

        DSSDocument extendedDocument = asicService.extendDocument(signedDocument, dssSignatureParameters);
        signedDocument = new InMemoryDocument(extendedDocument.getBytes(), signedDocument.getName(),
                signedDocument.getMimeType());

        signatures = new ArrayList<>();
        SignedDocumentValidator validator = ASiCXMLDocumentValidator.fromDocument(signedDocument);
        validate(validator);

        List<AdvancedSignature> signatureList = validator.getSignatures();
        for (AdvancedSignature advancedSignature : signatureList) {
            signatures.add(new BDocSignature((XAdESSignature) advancedSignature));
        }
    }

    @Override
    public String getVersion() {
        return null;
    }

    @Override
    public void extendTo(SignatureProfile profile) {
        logger.debug("");
        validationReport = null;
        isTimeMark = false;
        switch (profile) {
        case LT:
            extend(ASiC_E_BASELINE_LT);
            break;
        case LTA:
            extend(ASiC_E_BASELINE_LTA);
            break;
        case LT_TM:
            SignatureLevel currentSignatureLevel = dssSignatureParameters.getSignatureLevel();
            if (ASiC_E_BASELINE_LT.equals(currentSignatureLevel)
                    || ASiC_E_BASELINE_LTA.equals(currentSignatureLevel)
                    || ASiC_E_BASELINE_B.equals(currentSignatureLevel)) {
                throw new DigiDoc4JException(
                        "It is not possible to extend the signature from " + currentSignatureLevel + " to LT_TM");
            }
            isTimeMark = true;
            extend(ASiC_E_BASELINE_LT);
            break;
        default:
            throw new NotYetImplementedException();
        }
    }

    @Override
    public void setSignatureProfile(SignatureProfile profile) {
        logger.debug("");
        isTimeMark = false;
        switch (profile) {
        case B_BES:
            dssSignatureParameters.setSignatureLevel(ASiC_E_BASELINE_B);
            break;
        case LTA:
            dssSignatureParameters.setSignatureLevel(ASiC_E_BASELINE_LTA);
            break;
        case LT_TM:
            isTimeMark = true;
        default:
            dssSignatureParameters.setSignatureLevel(ASiC_E_BASELINE_LT);
        }
    }

    protected eu.europa.ec.markt.dss.parameter.SignatureParameters getDssSignatureParameters() {
        logger.debug("");
        return dssSignatureParameters;
    }
}