ee.sk.digidoc.SignedDoc.java Source code

Java tutorial

Introduction

Here is the source code for ee.sk.digidoc.SignedDoc.java

Source

/*
 * SignedDoc.java
 * PROJECT: JDigiDoc
 * DESCRIPTION: Digi Doc functions for creating
 *   and reading signed documents. 
 * AUTHOR:  Veiko Sinivee, Sunset Software O
 *==================================================
 * Copyright (C) AS Sertifitseerimiskeskus
 * This library 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 2.1 of the License, or (at your option) any later version.
 * This library 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
 * Lesser General Public License for more details.
 * GNU Lesser General Public Licence is available at
 * http://www.gnu.org/copyleft/lesser.html
 *==================================================
 */

package ee.sk.digidoc;

import java.io.Serializable;
//import java.util.zip.*;
//import org.apache.tools.zip.*;
import org.apache.commons.compress.archivers.zip.*;
import ee.sk.utils.ConvertUtils;
import java.util.ArrayList;
import java.util.Hashtable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.InvalidParameterException;
import java.security.MessageDigest;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import javax.crypto.Cipher;
import org.apache.log4j.Logger;
import ee.sk.digidoc.factory.DigiDocFactory;
import ee.sk.digidoc.factory.DigiDocVerifyFactory;
import ee.sk.digidoc.factory.DigiDocXmlGenFactory;
import ee.sk.digidoc.factory.DigiDocGenFactory;
import ee.sk.utils.ConfigManager;

/**
 * Represents an instance of signed doc
 * in DIGIDOC format. Contains one or more
 * DataFile -s and zero or more Signature -s.
 * @author  Veiko Sinivee
 * @version 1.0
 */
public class SignedDoc implements Serializable {
    private static final long serialVersionUID = 1L;
    /** digidoc format */
    private String m_format;
    /** format version */
    private String m_version;
    /** DataFile objects */
    private ArrayList m_dataFiles;
    /** Signature objects */
    private ArrayList m_signatures;
    /** bdoc manifest.xml file */
    private Manifest m_manifest;
    /** bdoc mime type */
    private String m_mimeType;
    /** xml-dsig namespace preifx */
    private String m_nsXmlDsig;
    /** xades namespace prefix */
    private String m_nsXades;
    /** asic namespace prefix */
    private String m_nsAsic;
    /** signature default profile */
    private String m_profile;
    /** container comment (bdoc2 lib ver and name. Maintaned by manifest file) */
    private String m_comment;
    /** hashtable of signature names and formats used during loading */
    private Hashtable m_sigFormats;
    private long m_size;
    /** original container path */
    private String m_path;
    /** original container filename without path */
    private String m_file;

    private static Logger m_logger = Logger.getLogger(SignedDoc.class);
    /** the only supported formats are SK-XML and DIGIDOC-XML */
    public static final String FORMAT_SK_XML = "SK-XML";
    public static final String FORMAT_DIGIDOC_XML = "DIGIDOC-XML";
    /**BDOC*/
    public static final String FORMAT_BDOC = "BDOC";
    /**application/vnd.bdoc*/
    public static final String FORMAT_BDOC_MIME = "application/vnd.bdoc";
    /** supported versions are 1.0 and 1.1 */
    public static final String VERSION_1_0 = "1.0";
    public static final String VERSION_1_1 = "1.1";
    public static final String VERSION_1_2 = "1.2";
    public static final String VERSION_1_3 = "1.3";
    /** bdoc versions are 1.0, 1.1 and 2.1 */
    public static final String BDOC_VERSION_1_0 = "1.0";
    public static final String BDOC_VERSION_1_1 = "1.1";
    public static final String BDOC_VERSION_2_1 = "2.1";
    /** bdoc profiles are - BES, T, C-L, TM, TS, TM-A, TS-A */
    public static final String BDOC_PROFILE_BES = "BES";
    public static final String BDOC_PROFILE_T = "T";
    public static final String BDOC_PROFILE_CL = "C-L";
    public static final String BDOC_PROFILE_TM = "TM";
    public static final String BDOC_PROFILE_TS = "TS";
    public static final String BDOC_PROFILE_TMA = "TM-A";
    public static final String BDOC_PROFILE_TSA = "TS-A";

    /** the only supported algorithm for ddoc is SHA1 */
    public static final String SHA1_DIGEST_ALGORITHM = "http://www.w3.org/2000/09/xmldsig#sha1";
    public static final String SHA1_DIGEST_TYPE = "SHA-1";
    public static final String SHA1_DIGEST_TYPE_BAD = "SHA-1-00";
    /** the only supported algorithm for bdoc is SHA256 */
    public static final String SHA256_DIGEST_ALGORITHM_1 = "http://www.w3.org/2001/04/xmlenc#sha256";
    public static final String SHA256_DIGEST_ALGORITHM_2 = "http://www.w3.org/2001/04/xmldsig-more#sha256";
    public static final String SHA256_DIGEST_TYPE = "SHA-256";
    /** algorithms for sha 224 **/
    public static final String SHA224_DIGEST_TYPE = "SHA-224";
    public static final String SHA224_DIGEST_ALGORITHM = "http://www.w3.org/2001/04/xmldsig-more#sha224";
    /** algorithms for sha 384 **/
    public static final String SHA384_DIGEST_TYPE = "SHA-384";
    public static final String SHA384_DIGEST_ALGORITHM = "http://www.w3.org/2001/04/xmldsig-more#sha384";
    /** sha-512 digest type */
    public static final String SHA512_DIGEST_TYPE = "SHA-512";
    public static final String SHA512_DIGEST_ALGORITHM = "http://www.w3.org/2001/04/xmlenc#sha512"; //"http://www.w3.org/2001/04/xmldsig-more#sha512";

    /** SHA1 digest data is allways 20 bytes */
    public static final int SHA1_DIGEST_LENGTH = 20;
    /** SHA224 digest data is allways 28 bytes */
    public static final int SHA224_DIGEST_LENGTH = 28;
    /** SHA256 digest data is allways 32 bytes */
    public static final int SHA256_DIGEST_LENGTH = 32;
    /** SHA512 digest data is allways 64 bytes */
    public static final int SHA512_DIGEST_LENGTH = 64;
    /** the only supported canonicalization method is 20010315 */
    public static final String CANONICALIZATION_METHOD_20010315 = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
    /** canonical xml 1.1 */
    public static final String CANONICALIZATION_METHOD_1_1 = "http://www.w3.org/2006/12/xml-c14n11";
    public static final String CANONICALIZATION_METHOD_2010_10_EXC = "http://www.w3.org/2001/10/xml-exc-c14n#";
    public static final String TRANSFORM_20001026 = "http://www.w3.org/TR/2000/CR-xml-c14n-20001026";
    /** the only supported signature method is RSA-SHA1 */
    public static final String RSA_SHA1_SIGNATURE_METHOD = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
    public static final String RSA_SHA224_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224";
    public static final String RSA_SHA256_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
    public static final String RSA_SHA384_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
    public static final String RSA_SHA512_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
    /** elliptic curve algorithms */
    public static final String ECDSA_SHA1_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1";
    public static final String ECDSA_SHA224_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224";
    public static final String ECDSA_SHA256_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256";
    public static final String ECDSA_SHA384_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384";
    public static final String ECDSA_SHA512_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512";

    /** the only supported transform is digidoc detatched transform */
    public static final String DIGIDOC_DETATCHED_TRANSFORM = "http://www.sk.ee/2002/10/digidoc#detatched-document-signature";
    public static final String ENVELOPED_TRANSFORM = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
    public static final String SIGNEDPROPERTIES_TYPE = "http://uri.etsi.org/01903#SignedProperties";
    /** XML-DSIG namespace */
    public static String xmlns_xmldsig = "http://www.w3.org/2000/09/xmldsig#";
    /** ETSI namespace */
    public static String xmlns_etsi = "http://uri.etsi.org/01903/v1.1.1#";
    /** DigiDoc namespace */
    public static String xmlns_digidoc13 = "http://www.sk.ee/DigiDoc/v1.3.0#";
    /** asic namespace */
    public static String xmlns_asic = "http://uri.etsi.org/02918/v1.2.1#";

    /** program & library name */
    public static final String LIB_NAME = Version.LIB_NAME;
    /** program & library version */
    public static final String LIB_VERSION = Version.LIB_VERSION;
    /** Xades namespace */
    public static String xmlns_xades_123 = "http://uri.etsi.org/01903/v1.3.2#";
    /** program & library name */
    public static final String SIG_FILE_NAME = "META-INF/signature";
    public static final String SIG_FILE_NAME_20 = "META-INF/signatures";
    public static final String MIMET_FILE_NAME = "mimetype";
    public static final String MIMET_FILE_CONTENT_10 = "application/vnd.bdoc-1.0";
    public static final String MIMET_FILE_CONTENT_11 = "application/vnd.bdoc-1.1";
    public static final String MIMET_FILE_CONTENT_20 = "application/vnd.etsi.asic-e+zip";
    public static final String MANIF_DIR_META_INF = "META-INF";
    public static final String MANIF_FILE_NAME = "META-INF/manifest.xml";
    public static final String MIME_SIGNATURE_BDOC_ = "signature/bdoc-";

    /** 
     * Creates new SignedDoc 
     * Initializes everything to null
     */
    public SignedDoc() {
        m_format = null;
        m_version = null;
        m_dataFiles = null;
        m_signatures = null;
        m_manifest = null;
        m_mimeType = null;
        m_nsXmlDsig = null;
        m_nsXades = null;
        m_nsAsic = null;
        m_file = null;
        m_path = null;
        m_comment = null;
    }

    /** 
     * Creates new SignedDoc 
     * @param format file format name
     * @param version file version number
     * @throws DigiDocException for validation errors
     */
    public SignedDoc(String format, String version) throws DigiDocException {
        setFormatAndVersion(format, version);
        m_dataFiles = null;
        m_signatures = null;
        m_manifest = null;
        m_mimeType = null;
        m_nsXmlDsig = null;
        m_nsXades = null;
        m_comment = null;
        if (format.equals(SignedDoc.FORMAT_BDOC)) {
            m_manifest = new Manifest();
            ManifestFileEntry fe = new ManifestFileEntry(getManifestEntry(version), "/");
            m_manifest.addFileEntry(fe);
            setDefaultNsPref(SignedDoc.FORMAT_BDOC);
        }
    }

    public void setDefaultNsPref(String format) {
        if (format.equals(SignedDoc.FORMAT_BDOC)) {
            m_nsXmlDsig = "ds";
            m_nsXades = "xades";
            m_nsAsic = "asic";
        }
        if (format.equals(SignedDoc.FORMAT_DIGIDOC_XML) || format.equals(SignedDoc.FORMAT_SK_XML)) {
            m_nsXmlDsig = null;
            m_nsXades = null;
            m_nsAsic = null;
        }
    }

    private String getManifestEntry(String ver) {
        if (ver.equals(BDOC_VERSION_1_0))
            return Manifest.MANIFEST_BDOC_MIME_1_0;
        else if (ver.equals(BDOC_VERSION_1_1))
            return Manifest.MANIFEST_BDOC_MIME_1_1;
        else
            return Manifest.MANIFEST_BDOC_MIME_2_0;
    }

    /**
     * Finds Manifest file-netry by path
     * @param fullPath file path in bdoc
     * @return file-netry if found
     */
    public ManifestFileEntry findManifestEntryByPath(String fullPath) {
        return m_manifest.findFileEntryByPath(fullPath);
    }

    /**
     * Accessor for format attribute
     * @return value of format attribute
     */
    public String getFormat() {
        return m_format;
    }

    /**
     * Mutator for format attribute
     * @param str new value for format attribute
     * @throws DigiDocException for validation errors
     */
    public void setFormat(String str) throws DigiDocException {
        DigiDocException ex = validateFormat(str);
        if (ex != null)
            throw ex;
        m_format = str;
    }

    /**
     * Accessor for all data-files atribute
     * @return all data-files
     */
    public ArrayList getDataFiles() {
        return m_dataFiles;
    }

    /**
     * Mutator for all data-files atribute
     * @param l list of data-files
     */
    public void setDataFiles(ArrayList l) {
        m_dataFiles = l;
    }

    /**
     * Accessor for all signatures atribute
     * @return all signatures
     */
    public ArrayList getSignatures() {
        return m_signatures;
    }

    /**
     * Accessor for size atribute
     * @return size in bytes
     */
    public long getSize() {
        return m_size;
    }

    /**
     * Mutator for size atribute
     * @param size in bytes
     */
    public void setSize(long l) {
        m_size = l;
    }

    /**
     * Accessor for file atribute
     * @return original container filename without path
     */
    public String getFile() {
        return m_file;
    }

    /**
     * Mutator for file atribute
     * @param fname original filename without path
     */
    public void setFile(String fname) {
        m_file = fname;
    }

    /**
     * Accessor for path atribute
     * @return original file path without filename
     */
    public String getPath() {
        return m_path;
    }

    /**
     * Mutator for size atribute
     * @param p original container path without filename
     */
    public void setPath(String p) {
        m_path = p;
    }

    /**
     * Accessor for comment attribute
     * @return value of comment attribute
     */
    public String getComment() {
        return m_comment;
    }

    /**
     * Mutator for comment attribute
     * @param s new value for comment attribute
     */
    public void setComment(String s) {
        m_comment = s;
    }

    /**
     * Registers a new signature format
     * @param sigId signature id
     * @param profile format/profile
     */
    public void addSignatureProfile(String sigId, String profile) {
        if (m_sigFormats == null)
            m_sigFormats = new Hashtable();
        if (m_logger.isDebugEnabled())
            m_logger.debug("Register signature: " + sigId + " profile: " + profile);
        m_sigFormats.put(sigId, profile);
    }

    /**
     * Returns signature profile
     * @param sigId signature id
     * @return profile
     */
    public String findSignatureProfile(String sigId) {
        return ((m_sigFormats != null && sigId != null) ? (String) m_sigFormats.get(sigId) : null);
    }

    /**
     * Accessor for xml-dsig ns prefix attribute
     * @return value of xml-dsig ns prefi attribute
     */
    public String getXmlDsigNs() {
        return m_nsXmlDsig;
    }

    /**
     * Mutator for xml-dsig ns prefi attribute
     * @param str new value for xml-dsig ns prefi attribute
     */
    public void setXmlDsigNs(String str) {
        m_nsXmlDsig = str;
    }

    /**
     * Accessor for xades ns prefix attribute
     * @return value of xades ns prefi attribute
     */
    public String getXadesNs() {
        return m_nsXades;
    }

    /**
     * Mutator for xades ns prefi attribute
     * @param str new value for xades ns prefi attribute
     */
    public void setXadesNs(String str) {
        m_nsXades = str;
    }

    /**
     * Accessor for asic ns prefix attribute
     * @return value of asic ns prefi attribute
     */
    public String getAsicNs() {
        return m_nsAsic;
    }

    /**
     * Mutator for asic ns prefi attribute
     * @param str new value for asic ns prefi attribute
     */
    public void setAsicNs(String str) {
        m_nsAsic = str;
    }

    /**
     * Accessor for profile attribute
     * @return value of profile attribute
     */
    public String getProfile() {
        return m_profile;
    }

    /**
     * Mutator for profile attribute
     * @param s new value for profile attribute
     */
    public void setProfile(String s) {
        m_profile = s;
    }

    /**
     * Helper method to validate a format
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateFormat(String str) {
        DigiDocException ex = null;
        if (str == null) {
            ex = new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "Format attribute is mandatory!", null);
        } else {
            if (!str.equals(FORMAT_BDOC) && !str.equals(FORMAT_SK_XML) && !str.equals(FORMAT_DIGIDOC_XML)) {
                ex = new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                        "Currently supports only SK-XML, DIGIDOC-XML and BDOC formats", null);
            }
            if (str.equals(SignedDoc.FORMAT_BDOC)) {
                if (m_manifest == null)
                    m_manifest = new Manifest();
                if (m_manifest.findFileEntryByPath("/") == null) {
                    ManifestFileEntry fe = new ManifestFileEntry(getManifestEntry(m_version), "/");
                    m_manifest.addFileEntry(fe);
                }
                setDefaultNsPref(SignedDoc.FORMAT_BDOC);
            }
        }
        return ex;
    }

    /**
     * Accessor for version attribute
     * @return value of version attribute
     */
    public String getVersion() {
        return m_version;
    }

    /**
     * Mutator for version attribute
     * @param str new value for version attribute
     * @throws DigiDocException for validation errors
     */
    public void setVersion(String str) throws DigiDocException {
        DigiDocException ex = validateVersion(str);
        if (ex != null)
            throw ex;
        m_version = str;
    }

    /**
     * Helper method to validate a version
     * @param str input data
     * @return exception or null for ok
     */
    private DigiDocException validateVersion(String str) {
        DigiDocException ex = null;
        if (str == null) {
            ex = new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "Version attribute is mandatory!", null);
        } else {
            if (m_format != null) {
                if (m_format.equals(FORMAT_SK_XML) && !str.equals(VERSION_1_0))
                    ex = new DigiDocException(DigiDocException.ERR_DIGIDOC_VERSION,
                            "Format SK-XML supports only version 1.0", null);
                if (m_format.equals(FORMAT_DIGIDOC_XML) && !str.equals(VERSION_1_1) && !str.equals(VERSION_1_2)
                        && !str.equals(VERSION_1_3))
                    ex = new DigiDocException(DigiDocException.ERR_DIGIDOC_VERSION,
                            "Format DIGIDOC-XML supports only versions 1.1, 1.2, 1.3", null);
                if (m_format.equals(FORMAT_BDOC) && !str.equals(BDOC_VERSION_2_1))
                    ex = new DigiDocException(DigiDocException.ERR_DIGIDOC_VERSION,
                            "Format BDOC supports only versions 2.1", null);
                // don't check for XADES and XADES_T - test formats for ETSI plugin tests
            }
        }
        return ex;
    }

    /**
     * Sets a combination of format and version and validates data
     * @param sFormat format string
     * @param sVersion version string
     * @throws DigiDocException in case of invalid format/version
     */
    public void setFormatAndVersion(String sFormat, String sVersion) throws DigiDocException {
        m_format = sFormat;
        m_version = sVersion;
        DigiDocException ex = validateFormatAndVersion();
        if (ex != null)
            throw ex;
    }

    /**
     * Helper method to validate both format and version
     * @return exception or null for ok
     */
    public DigiDocException validateFormatAndVersion() {
        DigiDocException ex = null;
        if (m_format == null || m_version == null) {
            return new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                    "Format and version attributes are mandatory!", null);
        }
        if (m_format.equals(FORMAT_DIGIDOC_XML) || m_format.equals(FORMAT_SK_XML)) {
            if (!m_version.equals(VERSION_1_3))
                return new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                        "Only format DIGIDOC-XML version 1.3 is supported!", null);
        } else if (m_format.equals(FORMAT_BDOC)) {
            if (!m_version.equals(BDOC_VERSION_2_1))
                return new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                        "Format BDOC supports only versions 2.1", null);
        } else {
            return new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT, "Invalid format attribute!", null);
        }
        return null;
    }

    /**
     * Accessor for manifest attribute
     * @return value of manifest attribute
     */
    public Manifest getManifest() {
        return m_manifest;
    }

    /**
     * Mutator for manifest element
     * @param m manifest element
     */
    public void setManifest(Manifest m) {
        m_manifest = m;
    }

    /**
     * Accessor for mime-type attribute
     * @return value of mime-type attribute
     */
    public String getMimeType() {
        return m_mimeType;
    }

    /**
     * Mutator for mime-type attribute
     * @param str new value for mime-type attribute
     */
    public void setMimeType(String str) {
        m_mimeType = str;
    }

    /**
     * return the count of DataFile objects
     * @return count of DataFile objects
     */
    public int countDataFiles() {
        return ((m_dataFiles == null) ? 0 : m_dataFiles.size());
    }

    /**
     * Removes temporary DataFile cache files
     */
    public void cleanupDfCache() {
        for (int i = 0; (m_dataFiles != null) && (i < m_dataFiles.size()); i++) {
            DataFile df = (DataFile) m_dataFiles.get(i);
            df.cleanupDfCache();
        }
    }

    public InputStream findDataFileAsStream(String dfName) {
        try {
            if (m_file != null) {
                StringBuffer sbName = new StringBuffer();
                if (m_path != null) {
                    sbName.append(m_path);
                    sbName.append(File.separator);
                }
                sbName.append(m_file);
                File fZip = new File(sbName.toString());
                if (fZip.isFile() && fZip.canRead()) {
                    ZipFile zis = new ZipFile(fZip);
                    ZipArchiveEntry ze = zis.getEntry(dfName);
                    if (ze != null) {
                        return zis.getInputStream(ze);
                    }
                }
            }
        } catch (Exception ex) {
            m_logger.error("Error reading bdoc: " + ex);
        }
        return null;
    }

    /**
     * return a new available DataFile id
     * @retusn new DataFile id
     */
    public String getNewDataFileId() {
        int nDf = 0;
        String id = "D" + nDf;
        boolean bExists = false;
        do {
            bExists = false;
            for (int d = 0; d < countDataFiles(); d++) {
                DataFile df = getDataFile(d);
                if (df.getId().equals(id)) {
                    nDf++;
                    id = "D" + nDf;
                    bExists = true;
                    continue;
                }
            }
        } while (bExists);
        return id;
    }

    /**
     * Adds a new DataFile to signed doc
     * @param inputFile input file name
     * @param mime files mime type
     * @param contentType DataFile's content type
     * @return new DataFile object
     */
    public DataFile addDataFile(File inputFile, String mime, String contentType) throws DigiDocException {
        DigiDocException ex1 = validateFormatAndVersion();
        if (ex1 != null)
            throw ex1;
        boolean bExists = false;
        for (int i = 0; i < countDataFiles(); i++) {
            DataFile df1 = getDataFile(i);
            if (df1.getFileName().equals(inputFile.getName()))
                bExists = true;
        }
        if (bExists && m_format.equals(FORMAT_BDOC)) {
            m_logger.error("Duplicate DataFile name: " + inputFile.getName());
            throw new DigiDocException(DigiDocException.ERR_DATA_FILE_FILE_NAME,
                    "Duplicate DataFile filename: " + inputFile.getName(), null);
        }
        DataFile df = new DataFile(getNewDataFileId(), contentType, inputFile.getAbsolutePath(), mime, this);
        if (inputFile.canRead())
            df.setSize(inputFile.length());
        addDataFile(df);
        if (m_format.equals(SignedDoc.FORMAT_BDOC)) {
            df.setId(inputFile.getName());
        }
        return df;
    }

    /**
     * Makes a copy of old file to be able to extrac data from it
     * during the creation of new file
     * @param sdocFile original existing container file
     * @return new temporary file
     * @throws DigiDocException
     */
    // TODO: research if this is necessary?
    /*private File copyOldFile(File sdocFile)
       throws DigiDocException
    {
       File fCopy = null;
       try {
     if(sdocFile.canRead()) { // if old file exists
      fCopy = File.createTempFile("sdoc", null);
      if(m_logger.isDebugEnabled())
         m_logger.debug("Copying original sdoc: " + sdocFile.getAbsolutePath() + " to: " + fCopy.getAbsolutePath());
      FileInputStream fis = new FileInputStream(sdocFile);
      FileOutputStream fos = new FileOutputStream(fCopy);
      byte[] data = new byte[2048];
      int n = 0;
      while((n = fis.read(data)) > 0)
         fos.write(data, 0, n);
      fis.close();
      fos.close();
     }
       } catch(Exception ex) {
      DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
       }
       return fCopy;
    }*/

    /**
     * Writes the SignedDoc to an output file
     * and automatically calculates DataFile sizes
     * and digests
     * @param outputFile output file name
     * @throws DigiDocException for all errors
     */
    public void writeToFile(File outputFile) throws DigiDocException {
        try {
            OutputStream os = new FileOutputStream(outputFile);
            // make a copy of old file if it exists
            //File fCopy = copyOldFile(outputFile);
            writeToStream(os);
            os.close();
            // delete temp file
            /*if(fCopy != null) {
               if(m_logger.isDebugEnabled())
             m_logger.debug("Deleting temp-file: " + fCopy.getAbsolutePath());
               fCopy.delete();
            }*/
        } catch (DigiDocException ex) {
            throw ex; // allready handled
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
        }
    }

    /**
     * Writes the SignedDoc to an output file
     * and automatically calculates DataFile sizes
     * and digests
     * @param outputFile output file name
     * @param fTempSdoc temporrary file, copy of original for copying items
     * @throws DigiDocException for all errors
     */
    public void writeToStream(OutputStream os/*, File fTempSdoc*/) throws DigiDocException {
        DigiDocException ex1 = validateFormatAndVersion();
        if (ex1 != null)
            throw ex1;
        try {
            DigiDocXmlGenFactory genFac = new DigiDocXmlGenFactory(this);
            if (m_format.equals(SignedDoc.FORMAT_BDOC)) {
                ZipArchiveOutputStream zos = new ZipArchiveOutputStream(os);
                zos.setEncoding("UTF-8");
                if (m_logger.isDebugEnabled())
                    m_logger.debug("OS: " + ((os != null) ? "OK" : "NULL"));
                // write mimetype
                if (m_logger.isDebugEnabled())
                    m_logger.debug("Writing: " + MIMET_FILE_NAME);
                ZipArchiveEntry ze = new ZipArchiveEntry(MIMET_FILE_NAME);
                if (m_comment == null)
                    m_comment = DigiDocGenFactory.getUserInfo(m_format, m_version);
                ze.setComment(m_comment);
                ze.setMethod(ZipArchiveEntry.STORED);
                java.util.zip.CRC32 crc = new java.util.zip.CRC32();
                if (m_version.equals(BDOC_VERSION_1_0)) {
                    ze.setSize(SignedDoc.MIMET_FILE_CONTENT_10.getBytes().length);
                    crc.update(SignedDoc.MIMET_FILE_CONTENT_10.getBytes());
                }
                if (m_version.equals(BDOC_VERSION_1_1)) {
                    ze.setSize(SignedDoc.MIMET_FILE_CONTENT_11.getBytes().length);
                    crc.update(SignedDoc.MIMET_FILE_CONTENT_11.getBytes());
                }
                if (m_version.equals(BDOC_VERSION_2_1)) {
                    ze.setSize(SignedDoc.MIMET_FILE_CONTENT_20.getBytes().length);
                    crc.update(SignedDoc.MIMET_FILE_CONTENT_20.getBytes());
                }
                ze.setCrc(crc.getValue());
                zos.putArchiveEntry(ze);
                if (m_version.equals(BDOC_VERSION_1_0)) {
                    zos.write(SignedDoc.MIMET_FILE_CONTENT_10.getBytes());
                }
                if (m_version.equals(BDOC_VERSION_1_1)) {
                    zos.write(SignedDoc.MIMET_FILE_CONTENT_11.getBytes());
                }
                if (m_version.equals(BDOC_VERSION_2_1)) {
                    zos.write(SignedDoc.MIMET_FILE_CONTENT_20.getBytes());
                }
                zos.closeArchiveEntry();
                // write manifest.xml
                if (m_logger.isDebugEnabled())
                    m_logger.debug("Writing: " + MANIF_FILE_NAME);
                ze = new ZipArchiveEntry(MANIF_DIR_META_INF);
                ze = new ZipArchiveEntry(MANIF_FILE_NAME);
                ze.setComment(DigiDocGenFactory.getUserInfo(m_format, m_version));
                zos.putArchiveEntry(ze);
                //if(m_logger.isDebugEnabled())
                //   m_logger.debug("Writing manif:\n" + m_manifest.toString());
                zos.write(m_manifest.toXML());
                zos.closeArchiveEntry();
                // write data files
                for (int i = 0; i < countDataFiles(); i++) {
                    DataFile df = getDataFile(i);
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("Writing DF: " + df.getFileName() + " content: " + df.getContentType()
                                + " df-cache: "
                                + ((df.getDfCacheFile() != null) ? df.getDfCacheFile().getAbsolutePath() : "NONE"));
                    InputStream is = null;
                    if (df.hasAccessToDataFile())
                        is = df.getBodyAsStream();
                    else
                        is = findDataFileAsStream(df.getFileName());
                    if (is != null) {
                        File dfFile = new File(df.getFileName());
                        String fileName = dfFile.getName();
                        ze = new ZipArchiveEntry(fileName);
                        if (df.getComment() == null)
                            df.setComment(DigiDocGenFactory.getUserInfo(m_format, m_version));
                        ze.setComment(df.getComment());
                        ze.setSize(dfFile.length());
                        ze.setTime(
                                (df.getLastModDt() != null) ? df.getLastModDt().getTime() : dfFile.lastModified());
                        zos.putArchiveEntry(ze);
                        byte[] data = new byte[2048];
                        int nRead = 0, nTotal = 0;
                        crc = new java.util.zip.CRC32();
                        while ((nRead = is.read(data)) > 0) {
                            zos.write(data, 0, nRead);
                            nTotal += nRead;
                            crc.update(data, 0, nRead);
                        }
                        ze.setSize(nTotal);
                        ze.setCrc(crc.getValue());
                        zos.closeArchiveEntry();
                        is.close();
                    }
                }
                for (int i = 0; i < countSignatures(); i++) {
                    Signature sig = getSignature(i);
                    String sFileName = sig.getPath();
                    if (sFileName == null) {
                        if (m_version.equals(BDOC_VERSION_2_1))
                            sFileName = SIG_FILE_NAME_20 + (i + 1) + ".xml";
                        else
                            sFileName = SIG_FILE_NAME + (i + 1) + ".xml";
                    }
                    if (!sFileName.startsWith("META-INF"))
                        sFileName = "META-INF/" + sFileName;
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("Writing SIG: " + sFileName + " orig: "
                                + ((sig.getOrigContent() != null) ? "OK" : "NULL"));
                    ze = new ZipArchiveEntry(sFileName);
                    if (sig.getComment() == null)
                        sig.setComment(DigiDocGenFactory.getUserInfo(m_format, m_version));
                    ze.setComment(sig.getComment());
                    String sSig = null;
                    if (sig.getOrigContent() != null)
                        sSig = new String(sig.getOrigContent(), "UTF-8");
                    else
                        sSig = sig.toString();
                    if (sSig != null && !sSig.startsWith("<?xml"))
                        sSig = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + sSig;
                    byte[] sdata = sSig.getBytes("UTF-8");
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("Writing SIG: " + sFileName + " xml:\n---\n "
                                + ((sSig != null) ? sSig : "NULL") + "\n---\n ");
                    ze.setSize(sdata.length);
                    crc = new java.util.zip.CRC32();
                    crc.update(sdata);
                    ze.setCrc(crc.getValue());
                    zos.putArchiveEntry(ze);
                    zos.write(sdata);
                    zos.closeArchiveEntry();
                }
                zos.close();
            } else if (m_format.equals(SignedDoc.FORMAT_DIGIDOC_XML)) { // ddoc format
                os.write(xmlHeader().getBytes());
                for (int i = 0; i < countDataFiles(); i++) {
                    DataFile df = getDataFile(i);
                    df.writeToFile(os);
                    os.write("\n".getBytes());
                }
                for (int i = 0; i < countSignatures(); i++) {
                    Signature sig = getSignature(i);
                    if (sig.getOrigContent() != null)
                        os.write(sig.getOrigContent());
                    else
                        os.write(genFac.signatureToXML(sig));
                    os.write("\n".getBytes());
                }
                os.write(xmlTrailer().getBytes());
            }
        } catch (DigiDocException ex) {
            throw ex; // allready handled
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_WRITE_FILE);
        }
    }

    /**
     * Adds a new DataFile object
     * @param attr DataFile object to add
     */
    public void addDataFile(DataFile df) throws DigiDocException {
        if (countSignatures() > 0)
            throw new DigiDocException(DigiDocException.ERR_SIGATURES_EXIST,
                    "Cannot add DataFiles when signatures exist!", null);
        boolean bExists = false;
        for (int i = 0; i < countDataFiles(); i++) {
            DataFile df1 = getDataFile(i);
            if (df1.getFileName().equals(df.getFileName()))
                bExists = true;
        }
        if (bExists && m_format.equals(FORMAT_BDOC)) {
            m_logger.error("Duplicate DataFile name: " + df.getFileName());
            throw new DigiDocException(DigiDocException.ERR_DATA_FILE_FILE_NAME,
                    "Duplicate DataFile filename: " + df.getFileName(), null);
        }
        if (m_format.equals(SignedDoc.FORMAT_BDOC)) {
            if (findManifestEntryByPath(df.getFileName()) == null) {
                df.setContentType(DataFile.CONTENT_BINARY);
                String sFile = df.getFileName();
                if (sFile != null && sFile.indexOf('/') != -1 || sFile.indexOf('\\') != -1) {
                    File fT = new File(sFile);
                    sFile = fT.getName();
                }
                if (sFile != null) {
                    ManifestFileEntry fe = new ManifestFileEntry(df.getMimeType(), sFile);
                    m_manifest.addFileEntry(fe);
                }
            }
        }
        if (m_dataFiles == null)
            m_dataFiles = new ArrayList();
        if (df.getId() == null)
            df.setId(getNewDataFileId());
        m_dataFiles.add(df);
    }

    /**
     * return the desired DataFile object
     * @param idx index of the DataFile object
     * @return desired DataFile object
     */
    public DataFile getDataFile(int idx) {
        if (m_dataFiles != null && idx >= 0 && idx < m_dataFiles.size())
            return (DataFile) m_dataFiles.get(idx);
        else
            return null;
    }

    /**
     * return the latest DataFile object
     * @return desired DataFile object
     */
    public DataFile getLastDataFile() {
        if (m_dataFiles != null && m_dataFiles.size() > 0)
            return (DataFile) m_dataFiles.get(m_dataFiles.size() - 1);
        else
            return null;
    }

    /**
     * Removes the datafile with the given index
     * @param idx index of the data file
     */
    public void removeDataFile(int idx) throws DigiDocException {
        if (countSignatures() > 0)
            throw new DigiDocException(DigiDocException.ERR_SIGATURES_EXIST,
                    "Cannot remove DataFiles when signatures exist!", null);
        DataFile df = getDataFile(idx);
        if (df != null) {
            m_dataFiles.remove(idx);
            if (m_manifest != null)
                m_manifest.removeFileEntryWithPath(df.getFileName());
        } else
            throw new DigiDocException(DigiDocException.ERR_DATA_FILE_ID, "Invalid DataFile index!", null);
    }

    /**
     * Returns DataFile with desired id
     * @param id Id attribute value
     * @return DataFile object or null if not found
     */
    public DataFile findDataFileById(String id) {
        for (int i = 0; (m_dataFiles != null) && (i < m_dataFiles.size()); i++) {
            DataFile df = (DataFile) m_dataFiles.get(i);
            if (df.getId() != null && id != null && df.getId().equals(id))
                return df;
        }
        return null;
    }

    /**
     * return the count of Signature objects
     * @return count of Signature objects
     */
    public int countSignatures() {
        return ((m_signatures == null) ? 0 : m_signatures.size());
    }

    /**
     * return a new available Signature id
     * @return new Signature id
     */
    public String getNewSignatureId() {
        int nS = 0;
        String id = "S" + nS;
        boolean bExists = false;
        do {
            bExists = false;
            for (int i = 0; i < countSignatures(); i++) {
                Signature sig = getSignature(i);
                if (sig.getId().equals(id)) {
                    nS++;
                    id = "S" + nS;
                    bExists = true;
                    continue;
                }
            }
        } while (bExists);
        return id;
    }

    /**
     * Find signature by id atribute value
     * @param sigId signature Id atribute value
     * @return signature object or null if not found
     */
    public Signature findSignatureById(String sigId) {
        for (int i = 0; i < countSignatures(); i++) {
            Signature sig = getSignature(i);
            if (sig.getId().equals(sigId))
                return sig;
        }
        return null;
    }

    /**
     * Find signature by path atribute value
     * @param path signature path atribute value (path in bdoc container)
     * @return signature object or null if not found
     */
    public Signature findSignatureByPath(String path) {
        for (int i = 0; i < countSignatures(); i++) {
            Signature sig = getSignature(i);
            if (sig.getPath() != null && sig.getPath().equals(path))
                return sig;
        }
        return null;
    }

    /**
     * Adds a new uncomplete signature to signed doc
     * @param cert signers certificate
     * @param claimedRoles signers claimed roles
     * @param adr signers address
     * @return new Signature object
     */
    public Signature prepareSignature(X509Certificate cert, String[] claimedRoles, SignatureProductionPlace adr)
            throws DigiDocException {
        DigiDocException ex1 = validateFormatAndVersion();
        if (ex1 != null)
            throw ex1;
        return DigiDocGenFactory.prepareXadesBES(this, m_profile, cert, claimedRoles, adr, null, null, null);
    }

    /**
     * Adds a new uncomplete signature to signed doc
     * @param cert signers certificate
     * @return new Signature object
     */
    public Signature prepareXadesTSignature(X509Certificate cert, String sigDatId, byte[] sigDatHash)
            throws DigiDocException {
        Signature sig = new Signature(this);
        sig.setId(getNewSignatureId());
        // create SignedInfo block
        SignedInfo si = new SignedInfo(sig, RSA_SHA1_SIGNATURE_METHOD, CANONICALIZATION_METHOD_20010315);
        // add DataFile references
        Reference ref = new Reference(si, "#" + sigDatId, SignedDoc.SHA1_DIGEST_ALGORITHM, sigDatHash,
                TRANSFORM_20001026);
        si.addReference(ref);
        sig.setSignedInfo(si);
        // create key info
        KeyInfo ki = new KeyInfo(cert);
        sig.setKeyInfo(ki);
        ki.setSignature(sig);
        CertValue cval = new CertValue(null, cert, CertValue.CERTVAL_TYPE_SIGNER, sig);
        sig.addCertValue(cval);
        CertID cid = new CertID(sig, cert, CertID.CERTID_TYPE_SIGNER);
        sig.addCertID(cid);
        addSignature(sig);
        UnsignedProperties usp = new UnsignedProperties(sig, null, null);
        sig.setUnsignedProperties(usp);
        return sig;
    }

    /**
     * Adds a new Signature object
     * @param attr Signature object to add
     */
    public void addSignature(Signature sig) {
        if (m_signatures == null)
            m_signatures = new ArrayList();
        m_signatures.add(sig);
        if (m_format != null && m_format.equals(SignedDoc.FORMAT_BDOC)) {
            Signature sig1 = null;
            if (sig.getPath() != null)
                sig1 = findSignatureByPath(sig.getPath());
            if (sig1 == null) {
                if (m_version.equals(BDOC_VERSION_2_1))
                    sig.setPath(SIG_FILE_NAME_20 + m_signatures.size() + ".xml");
                else
                    sig.setPath(SIG_FILE_NAME + m_signatures.size() + ".xml");
                // no manifest.xml entries for signatures in bdoc 2.0
                if (!m_version.equals(SignedDoc.BDOC_VERSION_2_1)) {
                    ManifestFileEntry fe = new ManifestFileEntry(
                            SignedDoc.MIME_SIGNATURE_BDOC_ + m_version + "/" + sig.getProfile(),
                            SignedDoc.SIG_FILE_NAME + m_signatures.size() + ".xml");
                    m_manifest.addFileEntry(fe);
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("Register in manifest new signature: " + sig.getId());
                }
            }
        }
    }

    /**
     * Adds a new Signature object by reading it from
     * input stream. This method can be used for example
     * during mobile signing process where the web-service
     * returns new signature in XML
     * @param is input stream
     */
    public void readSignature(InputStream is) throws DigiDocException {
        DigiDocFactory ddfac = ConfigManager.instance().getDigiDocFactory();
        Signature sig = ddfac.readSignature(this, is);
    }

    /**
     * return the desired Signature object
     * @param idx index of the Signature object
     * @return desired Signature object
     */
    public Signature getSignature(int idx) {
        if (m_signatures != null && idx >= 0 && idx < m_signatures.size())
            return (Signature) m_signatures.get(idx);
        else
            return null;
    }

    /**
     * Removes the desired Signature object
     * @param idx index of the Signature object
     */
    public void removeSignature(int idx) throws DigiDocException {
        if (m_signatures != null && idx >= 0 && idx < m_signatures.size())
            m_signatures.remove(idx);
        else
            throw new DigiDocException(DigiDocException.ERR_SIGNATURE_ID, "Invalid signature index: " + idx, null);
    }

    /**
     * return the latest Signature object
     * @return desired Signature object
     */
    public Signature getLastSignature() {
        if (m_signatures != null && m_signatures.size() > 0)
            return (Signature) m_signatures.get(m_signatures.size() - 1);
        else
            return null;
    }

    /** 
     * Deletes last signature
     */
    public void removeLastSiganture() {
        if (m_signatures.size() > 0)
            m_signatures.remove(m_signatures.size() - 1);
    }

    /**
     * Removes signatures without value. Temporary signatures created
     * during signing process but without completing the process
     */
    public int removeSignaturesWithoutValue() {
        int nRemove = 0;
        boolean bOk = true;
        do {
            bOk = true;
            for (int i = 0; (m_signatures != null) && (i < m_signatures.size()) && bOk; i++) {
                Signature sig = (Signature) m_signatures.get(i);
                if (sig.getSignatureValue() == null || sig.getSignatureValue().getValue() == null
                        || sig.getSignatureValue().getValue().length == 0) {
                    m_signatures.remove(sig);
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("Remove invalid sig: " + sig.getId());
                    bOk = false;
                    nRemove++;
                }
            }
        } while (!bOk);
        return nRemove;
    }

    /**
     * Helper method to validate the whole
     * SignedDoc object
     * @param bStrong flag that specifies if Id atribute value is to
     * be rigorously checked (according to digidoc format) or only
     * as required by XML-DSIG
     * @return a possibly empty list of DigiDocException objects
     */
    public ArrayList validate(boolean bStrong) {
        ArrayList errs = new ArrayList();
        DigiDocException ex = validateFormat(m_format);
        if (ex != null)
            errs.add(ex);
        ex = validateVersion(m_version);
        if (ex != null)
            errs.add(ex);
        if (m_format != null && m_version != null && (m_format.equals(SignedDoc.FORMAT_SK_XML)
                || (m_format.equals(SignedDoc.FORMAT_DIGIDOC_XML)
                        && (m_version.equals(SignedDoc.VERSION_1_1) || m_version.equals(SignedDoc.VERSION_1_2)))
                || (m_format.equals(SignedDoc.FORMAT_BDOC)
                        && (m_version.equals(SignedDoc.VERSION_1_0) || m_version.equals(SignedDoc.VERSION_1_1))))) {
            if (m_logger.isDebugEnabled())
                m_logger.debug("Old and unsupported format: " + m_format + " version: " + m_version);
            ex = new DigiDocException(DigiDocException.ERR_OLD_VER,
                    "Old and unsupported format: " + m_format + " version: " + m_version, null);
            errs.add(ex);
        }
        if (m_profile != null && (m_profile.equals(SignedDoc.BDOC_PROFILE_T)
                || m_profile.equals(SignedDoc.BDOC_PROFILE_TS) || m_profile.equals(SignedDoc.BDOC_PROFILE_TSA))) {
            if (m_logger.isDebugEnabled())
                m_logger.debug("T, TS and TSA profiles are currently not supported!");
            ex = new DigiDocException(DigiDocException.ERR_VERIFY,
                    "T, TS and TSA profiles are currently not supported!", null);
            errs.add(ex);
        }

        try {
            if (getFormat() != null && getFormat().equals(SignedDoc.FORMAT_BDOC))
                DigiDocVerifyFactory.verifyManifestEntries(this, errs);
            for (int i = 0; i < countDataFiles(); i++) {
                DataFile df = getDataFile(i);
                ArrayList e = df.validate(bStrong);
                if (!e.isEmpty())
                    errs.addAll(e);
            }
            for (int i = 0; i < countSignatures(); i++) {
                Signature sig = getSignature(i);
                ArrayList e = sig.validate();
                if (!e.isEmpty())
                    errs.addAll(e);
            }
            for (int i = 0; i < countSignatures(); i++) {
                Signature sig1 = getSignature(i);
                for (int j = 0; j < countSignatures(); j++) {
                    Signature sig2 = getSignature(j);
                    if (getFormat() != null && getFormat().equals(SignedDoc.FORMAT_BDOC) && sig2.getId() != null
                            && sig1.getId() != null && !sig2.getId().equals(sig1.getId()) && sig2.getPath() != null
                            && sig1.getPath() != null && sig2.getPath().equals(sig1.getPath())) {
                        if (m_logger.isDebugEnabled())
                            m_logger.debug("Signatures: " + sig1.getId() + " and " + sig2.getId()
                                    + " are in same file: " + sig1.getPath());
                        ex = new DigiDocException(DigiDocException.ERR_PARSE_XML,
                                "More than one signature in signatures.xml file is unsupported", null);
                        errs.add(ex);
                    }
                }
            }
        } catch (DigiDocException ex2) {
            errs.add(ex2);
        }
        return errs;
    }

    public static boolean hasFatalErrs(ArrayList lerrs) {
        for (int i = 0; (lerrs != null) && (i < lerrs.size()); i++) {
            DigiDocException ex = (DigiDocException) lerrs.get(i);
            if (ex.getCode() == DigiDocException.ERR_PARSE_XML) {
                return true;
            }
        }
        return false;
    }

    /**
     * Helper method to verify the whole SignedDoc object. 
     * Use this method to verify all signatures
     * @param checkDate Date on which to check the signature validity
     * @param demandConfirmation true if you demand OCSP confirmation from
     * every signature
     * @return a possibly empty list of DigiDocException objects
     */
    public ArrayList verify(boolean checkDate, boolean demandConfirmation) {
        ArrayList errs = validate(true);
        // check fatal errs
        if (hasFatalErrs(errs))
            return errs;
        // verification
        for (int i = 0; i < countSignatures(); i++) {
            Signature sig = getSignature(i);
            ArrayList e = sig.verify(this, checkDate, demandConfirmation);
            if (!e.isEmpty())
                errs.addAll(e);
        }
        if (countSignatures() == 0) {
            errs.add(new DigiDocException(DigiDocException.ERR_NOT_SIGNED, "This document is not signed!", null));
        }
        return errs;
    }

    /**
     * Helper method to create the xml header
     * @return xml header
     */
    private String xmlHeader() {
        StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        if (m_format.equals(FORMAT_DIGIDOC_XML)) {
            sb.append("<SignedDoc format=\"");
            sb.append(m_format);
            sb.append("\" version=\"");
            sb.append(m_version);
            sb.append("\"");
            // namespace
            if (m_version.equals(VERSION_1_3)) {
                sb.append(" xmlns=\"");
                sb.append(xmlns_digidoc13);
                sb.append("\"");
            }
            sb.append(">\n");
        }
        return sb.toString();
    }

    /**
     * Helper method to create the xml trailer
     * @return xml trailer
     */
    private String xmlTrailer() {
        if (m_format.equals(FORMAT_DIGIDOC_XML))
            return "\n</SignedDoc>";
        else
            return "";
    }

    /**
     * Converts the SignedDoc to XML form
     * @return XML representation of SignedDoc
     */
    public String toXML() throws DigiDocException {
        StringBuffer sb = new StringBuffer(xmlHeader());
        for (int i = 0; i < countDataFiles(); i++) {
            DataFile df = getDataFile(i);
            String str = df.toString();
            sb.append(str);
            sb.append("\n");
        }
        for (int i = 0; i < countSignatures(); i++) {
            Signature sig = getSignature(i);
            String str = sig.toString();
            sb.append(str);
            sb.append("\n");
        }
        sb.append(xmlTrailer());
        return sb.toString();
    }

    /**
     * return the stringified form of SignedDoc
     * @return SignedDoc string representation
     */
    public String toString() {
        String str = null;
        try {
            str = toXML();
        } catch (Exception ex) {
        }
        return str;
    }

    /**
     * Computes an SHA1 digest
     * @param data input data
     * @return SHA1 digest
     */
    public static byte[] digest(byte[] data) throws DigiDocException {
        return digestOfType(data, SHA1_DIGEST_TYPE);
    }

    /**
     * Computes a digest
     * @param data input data
     * @param digType digest type
     * @return digest value
     */
    public static byte[] digestOfType(byte[] data, String digType) throws DigiDocException {
        byte[] dig = null;
        try {
            MessageDigest sha = MessageDigest.getInstance(digType, "BC");
            sha.update(data);
            dig = sha.digest();
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_CALCULATE_DIGEST);
        }
        return dig;
    }

    /**
     * Retrieves DN part with given field name
     * @param sDn DN in string form according to RFC1779 or later
     * @param sField field name
     * @param sOid OID value if known as alternative field name
     * @return field content
     */
    private static String getDnPart(String sDn, String sField, String sOid) {
        if (sDn != null && sDn.length() > 0) {
            String s = sField + "=";
            boolean bQ = false;
            int n1 = sDn.toUpperCase().indexOf(s.toUpperCase());
            if (n1 == -1 && sOid != null) {
                s = "OID." + sOid + "=";
                n1 = sDn.toUpperCase().indexOf(s.toUpperCase());
            }
            if (n1 >= 0) {
                n1 += s.length();
                if (sDn.charAt(n1) == '\"') {
                    bQ = true;
                    n1++;
                }
                int n2 = sDn.indexOf(bQ ? "\", " : ", ", n1);
                if (n2 == -1)
                    n2 = sDn.length();
                if (n2 > n1 && n2 <= sDn.length())
                    return sDn.substring(n1, n2);
            }
        }
        return null;
    }

    /**
     * return certificate owners first name
     * @return certificate owners first name or null
     */
    public static String getSubjectFirstName(X509Certificate cert) {
        String dn = getDN(cert);
        String name = null;
        String cn = getDnPart(dn, "CN", null);
        if (cn != null) {
            int idx1 = 0;
            while (idx1 < cn.length() && cn.charAt(idx1) != ',')
                idx1++;
            if (idx1 < cn.length())
                idx1++;
            int idx2 = idx1;
            while (idx2 < cn.length() && cn.charAt(idx2) != ',' && cn.charAt(idx2) != '/')
                idx2++;
            name = cn.substring(idx1, idx2);
        }
        return name;
    }

    /**
     * return certificate owners last name
     * @return certificate owners last name or null
     */
    public static String getSubjectLastName(X509Certificate cert) {
        String dn = getDN(cert);
        String name = null;
        String cn = getDnPart(dn, "CN", null);
        if (cn != null) {
            int idx1 = 0;
            while (idx1 < cn.length() && !Character.isLetter(cn.charAt(idx1)))
                idx1++;
            int idx2 = idx1;
            while (idx2 < cn.length() && cn.charAt(idx2) != ',' && dn.charAt(idx2) != '/')
                idx2++;
            name = cn.substring(idx1, idx2);
        }
        return name;
    }

    /**
     * return certificate owners personal code
     * @return certificate owners personal code or null
     */
    public static String getSubjectPersonalCode(X509Certificate cert) {
        String dn = getDN(cert);
        String code = getDnPart(dn, "SERIALNUMBER", "2.5.4.5");
        if (code != null)
            return code;
        String cn = getDnPart(dn, "CN", null);
        if (cn != null) {
            int idx1 = 0;
            while (idx1 < cn.length() && !Character.isDigit(cn.charAt(idx1)))
                idx1++;
            int idx2 = idx1;
            while (idx2 < cn.length() && Character.isDigit(cn.charAt(idx2)))
                idx2++;
            if (idx2 > idx1 + 7)
                code = cn.substring(idx1, idx2);
        }
        return code;
    }

    /**
     * Returns certificates DN field in RFC1779 format
     * @param cert certificate
     * @return DN field
     */
    private static String getDN(X509Certificate cert) {
        return cert.getSubjectX500Principal().getName("RFC1779");
    }

    /**
     * return CN part of DN
     * @return CN part of DN or null
     */
    public static String getCommonName(String dn) {
        return getDnPart(dn, "CN", null);
    }

    /**
     * Reads X509 certificate from a data stream
     * @param data input data in Base64 form
     * @return X509Certificate object
     * @throws EFormException for all errors
     */
    public static X509Certificate readCertificate(byte[] data) throws DigiDocException {
        X509Certificate cert = null;
        try {
            ByteArrayInputStream certStream = new ByteArrayInputStream(data);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) cf.generateCertificate(certStream);
            certStream.close();
        } catch (Exception ex) {
            m_logger.error("Error reading certificate: " + ex);
            //DigiDocException.handleException(ex, DigiDocException.ERR_READ_CERT);
            return null;
        }
        return cert;
    }

    /**
     * Reads in data file
     * @param inFile input file
     */
    public static byte[] readFile(File inFile) throws IOException, FileNotFoundException {
        byte[] data = null;
        FileInputStream is = new FileInputStream(inFile);
        DataInputStream dis = new DataInputStream(is);
        data = new byte[dis.available()];
        dis.readFully(data);
        dis.close();
        is.close();
        return data;
    }

    /**
     * Reads the cert from a file
     * @param certFile certificates file name
     * @return certificate object
     */
    public static X509Certificate readCertificate(File certFile) throws DigiDocException {
        X509Certificate cert = null;
        try {
            FileInputStream fis = new FileInputStream(certFile);
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) certificateFactory.generateCertificate(fis);
            fis.close();
            //byte[] data = readFile(certFile);
            //cert = readCertificate(data);
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        }
        return cert;
    }

    private static final String PEM_HDR1 = "-----BEGIN CERTIFICATE-----\n";
    private static final String PEM_HDR2 = "\n-----END CERTIFICATE-----";

    /**
     * Writes the cert from a file
     * @param cert certificate
     * @param certFile certificates file name
     * @return true for success
     */
    public static boolean writeCertificate(X509Certificate cert, File certFile) throws DigiDocException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(certFile);
            fos.write(PEM_HDR1.getBytes());
            fos.write(Base64Util.encode(cert.getEncoded()).getBytes());
            fos.write(PEM_HDR2.getBytes());
            fos.close();
            fos = null;
            //byte[] data = readFile(certFile);
            //cert = readCertificate(data);
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (Exception ex2) {
                    m_logger.error("Error closing streams: " + ex2);
                }
            }
        }
        return false;
    }

    /**
     * Reads the cert from a file, URL or from another
     * location somewhere in the CLASSPATH such as
     * in the librarys jar file.
     * @param certLocation certificates file name,
     * or URL. You can use url in form jar://<location> to read
     * a certificate from the car file or some other location in the
     * CLASSPATH
     * @return certificate object
     */
    public static X509Certificate readCertificate(String certLocation) throws DigiDocException {
        X509Certificate cert = null;
        InputStream isCert = null;
        try {
            URL url = null;
            if (certLocation.startsWith("http")) {
                url = new URL(certLocation);
                isCert = url.openStream();
            } else if (certLocation.startsWith("jar://")) {
                ClassLoader cl = ConfigManager.instance().getClass().getClassLoader();
                isCert = cl.getResourceAsStream(certLocation.substring(6));
            } else {
                isCert = new FileInputStream(certLocation);
            }
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) certificateFactory.generateCertificate(isCert);
            isCert.close();
            isCert = null;
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_READ_FILE);
        } finally {
            if (isCert != null) {
                try {
                    isCert.close();
                } catch (Exception ex2) {
                    m_logger.error("Error closing streams: " + ex2);
                }
            }
        }
        return cert;
    }

    /**
     * Helper method for comparing
     * digest values
     * @param dig1 first digest value
     * @param dig2 second digest value
     * @return true if they are equal
     */
    public static boolean compareDigests(byte[] dig1, byte[] dig2) {
        boolean ok = (dig1 != null) && (dig2 != null) && (dig1.length == dig2.length);
        for (int i = 0; ok && (i < dig1.length); i++)
            if (dig1[i] != dig2[i])
                ok = false;
        return ok;
    }

    /** 
     * Converts a hex string to byte array
     * @param hexString input data
     * @return byte array
     */
    public static byte[] hex2bin(String hexString) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            for (int i = 0; (hexString != null) && (i < hexString.length()); i += 2) {
                String tmp = hexString.substring(i, i + 2);
                Integer x = new Integer(Integer.parseInt(tmp, 16));
                bos.write(x.byteValue());
            }
        } catch (Exception ex) {
            m_logger.error("Error converting hex string: " + ex);
        }
        return bos.toByteArray();
    }

    /**
     * Converts a byte array to hex string
     * @param arr byte array input data
     * @return hex string
     */
    public static String bin2hex(byte[] arr) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < arr.length; i++) {
            String str = Integer.toHexString((int) arr[i]);
            if (str.length() == 2)
                sb.append(str);
            if (str.length() < 2) {
                sb.append("0");
                sb.append(str);
            }
            if (str.length() > 2)
                sb.append(str.substring(str.length() - 2));
        }
        return sb.toString();
    }

}