All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ee.sk.digidoc.SignedDoc Maven / Gradle / Ivy

/*
 * 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("\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) && df.getFileName() != null) {
    		df.setContentType(DataFile.CONTENT_BINARY);
        	String sFile = df.getFileName();
        	if(sFile.indexOf('/') != -1 || sFile.indexOf('\\') != -1) {
        		File fT = new File(sFile);
        		sFile = fT.getName();
        	}
    		if(findManifestEntryByPath(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("\n");
        if(m_format.equals(FORMAT_DIGIDOC_XML)) {
        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";
    	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:// 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();
    }
    
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy