ee.sk.digidoc.factory.SAXDigiDocFactory.java Source code

Java tutorial

Introduction

Here is the source code for ee.sk.digidoc.factory.SAXDigiDocFactory.java

Source

/*
 * SAXDigiDocFactory.java
 * PROJECT: JDigiDoc
 * DESCRIPTION: Digi Doc functions for creating
 *   and reading signed documents.
 * AUTHOR:  Veiko Sinivee, S|E|B IT Partner Estonia
 *==================================================
 * 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.factory;

import ee.sk.digidoc.*;
import ee.sk.utils.ConvertUtils;
import ee.sk.utils.ConfigManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.security.MessageDigest;

import java.util.Stack;
import java.util.ArrayList;
import java.util.Hashtable;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import java.security.cert.X509Certificate;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.tsp.TimeStampToken;

// Logging classes
import org.apache.log4j.Logger;

/**
 * SAX implementation of DigiDocFactory
 * Provides methods for reading a DigiDoc file
 * @author  Veiko Sinivee
 * @version 1.0
 */
public class SAXDigiDocFactory extends DefaultHandler implements DigiDocFactory {
    private Stack m_tags;
    private SignedDoc m_doc;
    private String m_strSigValTs, m_strSigAndRefsTs;
    private StringBuffer m_sbCollectChars;
    private StringBuffer m_sbCollectItem;
    private StringBuffer m_sbCollectSignature;
    private boolean m_bCollectDigest;
    private String m_xmlnsAttr;
    /** This mode means collect SAX events into xml data
     * and is used to collect all <DataFile>, <SignedInfo> and
     * <SignedProperties> content. Also servers as level of
     * embedded DigiDoc files. Initially it should be 0. If
     * we start collecting data then it's 1 and if we find
     * another SignedDoc inside a DataFile then it will be incremented
     * in order to know which is the correct </DataFile> tag to leave
     * the collect mode
     */
    private int m_nCollectMode;
    /** log4j logger */
    private Logger m_logger = null;
    /** root/CA certificates */
    private Hashtable m_rootCerts;
    /** calculation of digest */
    private MessageDigest m_digest;
    /** temp output stream used to cache DataFile content */
    private FileOutputStream m_dfCacheOutStream;

    /**
     * Creates new SAXDigiDocFactory
     * and initializes the variables
     */
    public SAXDigiDocFactory() {
        m_tags = new Stack();
        m_doc = null;
        m_sbCollectSignature = null;
        m_xmlnsAttr = null;
        m_rootCerts = null;
        m_sbCollectItem = null;
        m_digest = null;
        m_bCollectDigest = false;
        m_dfCacheOutStream = null;
        m_logger = Logger.getLogger(SAXDigiDocFactory.class);
    }

    /**
     * Helper method to update sha1 digest with some data
     * @param data
     */
    private void updateDigest(byte[] data) {
        try {
            // if not inited yet then initialize digest
            if (m_digest == null)
                m_digest = MessageDigest.getInstance("SHA-1");
            m_digest.update(data);
        } catch (Exception ex) {
            m_logger.error("Error calculating digest: " + ex);
            //ex.printStackTrace();
        }
    }

    /**
     * Helper method to calculate the digest result and 
     * reset digest
     * @return sha-1 digest value
     */
    private byte[] getDigest() {
        byte[] digest = null;
        try {
            // if not inited yet then initialize digest
            digest = m_digest.digest();
            m_digest = null; // reset for next calculation
        } catch (Exception ex) {
            m_logger.error("Error calculating digest: " + ex);
            //ex.printStackTrace();
        }
        return digest;
    }

    /**
     * initializes the implementation class
     */
    public void init() throws DigiDocException {
        if (m_rootCerts == null)
            initCACerts();
    }

    /** 
     * initializes the CA certificates  
     */
    public void initCACerts() throws DigiDocException {
        try {
            m_rootCerts = new Hashtable();
            if (m_logger.isDebugEnabled())
                m_logger.debug("Loading CA certs");
            int nCerts = ConfigManager.instance().getIntProperty("DIGIDOC_CA_CERTS", 0);
            for (int i = 0; i < nCerts; i++) {
                String certFile = ConfigManager.instance().getProperty("DIGIDOC_CA_CERT" + (i + 1));
                if (m_logger.isDebugEnabled())
                    m_logger.debug("CA: " + ("DIGIDOC_CA_CERT" + (i + 1)) + " file: " + certFile);
                X509Certificate cert = SignedDoc.readCertificate(certFile);
                if (cert != null) {
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("CA subject: " + cert.getSubjectDN() + " issuer: "
                                + cert.getIssuerX500Principal().getName("RFC1779"));
                    m_rootCerts.put(cert.getSubjectX500Principal().getName("RFC1779"), cert);
                }
            }
        } catch (Exception e) {
            DigiDocException.handleException(e, DigiDocException.ERR_CA_CERT_READ);
        }
    }

    /**
     * Verifies that the signers cert
     * has been signed by at least one
     * of the known root certs
     * @param cert certificate to check
     */
    public boolean verifyCertificate(X509Certificate cert) throws DigiDocException {
        boolean rc = false;
        if (m_rootCerts != null && !m_rootCerts.isEmpty()) {
            try {
                X509Certificate rCert = (X509Certificate) m_rootCerts
                        .get(cert.getIssuerX500Principal().getName("RFC1779"));
                if (rCert != null) {
                    cert.verify(rCert.getPublicKey());
                    rc = true;
                }
            } catch (Exception ex) {
                DigiDocException.handleException(ex, DigiDocException.ERR_UNKNOWN_CA_CERT);
            }
        }
        return rc;
    }

    /**
     * Finds the CA for this certificate
     * if the root-certs table is not empty
     * @param cert certificate to search CA for
     * @return CA certificate
     */
    public X509Certificate findCAforCertificate(X509Certificate cert) {
        X509Certificate caCert = null;
        if (cert != null && m_rootCerts != null && !m_rootCerts.isEmpty()) {
            //String cn = SignedDoc.getCommonName(cert.getIssuerX500Principal().getName("RFC1779"));
            String dn = cert.getIssuerX500Principal().getName("RFC1779");
            if (m_logger.isDebugEnabled())
                m_logger.debug("Find CA cert for issuer: " + dn);
            caCert = (X509Certificate) m_rootCerts.get(dn);
            if (m_logger.isDebugEnabled())
                m_logger.debug("CA: " + ((caCert == null) ? "NULL" : "OK"));
        }
        return caCert;
    }

    /**
     * Reads in a DigiDoc file
     * @param fileName file name
     * @return signed document object if successfully parsed
     */
    public SignedDoc readSignedDoc(String fileName) throws DigiDocException {
        // Use an instance of ourselves as the SAX event handler
        SAXDigiDocFactory handler = this;
        // Use the default (non-validating) parser
        SAXParserFactory factory = SAXParserFactory.newInstance();
        //factory.setNamespaceAware(true);
        try {
            SAXParser saxParser = factory.newSAXParser();
            FileInputStream is = new FileInputStream(fileName);
            saxParser.parse(is, handler);
            is.close();
        } catch (SAXDigiDocException ex) {
            throw ex.getDigiDocException();
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML);
        }
        if (m_doc == null)
            throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                    "This document is not in digidoc format", null);
        return m_doc;
    }

    /**
     * Reads in a DigiDoc file
     * @param digiDocStream opened stream with DigiDoc data
     * The user must open and close it.
     * @return signed document object if successfully parsed
     */
    public SignedDoc readSignedDoc(InputStream digiDocStream) throws DigiDocException {
        // Use an instance of ourselves as the SAX event handler
        SAXDigiDocFactory handler = this;
        // Use the default (non-validating) parser
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(digiDocStream, handler);
        } catch (SAXDigiDocException ex) {
            throw ex.getDigiDocException();
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML);
        }
        if (m_doc == null)
            throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                    "This document is not in digidoc format", null);
        return m_doc;
    }

    /**
     * Reads in only one <Signature>
     * @param sdoc SignedDoc to add this signature to
     * @param sigStream opened stream with Signature data
     * The user must open and close it.
     * @return signed document object if successfully parsed
     */
    public Signature readSignature(SignedDoc sdoc, InputStream sigStream) throws DigiDocException {
        // Use an instance of ourselves as the SAX event handler
        SAXDigiDocFactory handler = this;
        // Use the default (non-validating) parser
        SAXParserFactory factory = SAXParserFactory.newInstance();
        m_doc = sdoc;
        //m_doc.addSignature(new Signature(sdoc));
        m_nCollectMode = 0;
        try {
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(sigStream, handler);
        } catch (SAXDigiDocException ex) {
            throw ex.getDigiDocException();
        } catch (Exception ex) {
            DigiDocException.handleException(ex, DigiDocException.ERR_PARSE_XML);
        }
        if (m_doc.getLastSignature() == null)
            throw new DigiDocException(DigiDocException.ERR_DIGIDOC_FORMAT,
                    "This document is not in Signature format", null);
        return m_doc.getLastSignature();
    }

    /**
     * Helper method to canonicalize a piece of xml
     * @param xml data to be canonicalized
     * @return canonicalized xml
     */
    private String canonicalizeXml(String xml) {
        try {
            CanonicalizationFactory canFac = ConfigManager.instance().getCanonicalizationFactory();
            byte[] tmp = canFac.canonicalize(xml.getBytes("UTF-8"), SignedDoc.CANONICALIZATION_METHOD_20010315);
            return new String(tmp, "UTF-8");
        } catch (Exception ex) {
            m_logger.error("Canonicalizing exception: " + ex);
        }
        return null;
    }

    public SignedDoc getSignedDoc() {
        return m_doc;
    }

    public Signature getLastSignature() {
        return m_doc.getLastSignature();
    }

    /**
     * Start Document handler
     */
    public void startDocument() throws SAXException {
        m_nCollectMode = 0;
        m_xmlnsAttr = null;
        m_dfCacheOutStream = null;
    }

    private void findCertIDandCertValueTypes(Signature sig) {
        if (m_logger.isDebugEnabled())
            m_logger.debug("Sig: " + sig.getId() + " certids: " + sig.countCertIDs());
        for (int i = 0; (sig != null) && (i < sig.countCertIDs()); i++) {
            CertID cid = sig.getCertID(i);
            if (m_logger.isDebugEnabled())
                m_logger.debug("CertId: " + cid.getId() + " type: " + cid.getType() + " nr: " + cid.getSerial());
            if (cid.getType() == CertID.CERTID_TYPE_UNKNOWN) {
                CertValue cval = sig.findCertValueWithSerial(cid.getSerial());
                if (cval != null) {
                    String cn = null;
                    try {
                        cn = SignedDoc.getCommonName(cval.getCert().getSubjectDN().getName());
                        if (m_logger.isDebugEnabled())
                            m_logger.debug("CertId type: " + cid.getType() + " nr: " + cid.getSerial() + " cval: "
                                    + cval.getId() + " CN: " + cn);
                        if (ConvertUtils.isKnownOCSPCert(cn)) {
                            if (m_logger.isInfoEnabled())
                                m_logger.debug("Cert: " + cn + " is OCSP responders cert");
                            cid.setType(CertID.CERTID_TYPE_RESPONDER);
                            cval.setType(CertValue.CERTVAL_TYPE_RESPONDER);
                        }
                        if (ConvertUtils.isKnownTSACert(cn)) {
                            if (m_logger.isDebugEnabled())
                                m_logger.debug("Cert: " + cn + " is TSA cert");
                            cid.setType(CertID.CERTID_TYPE_TSA);
                            cval.setType(CertValue.CERTVAL_TYPE_TSA);
                            if (m_logger.isDebugEnabled())
                                m_logger.debug("CertId: " + cid.getId() + " type: " + cid.getType() + " nr: "
                                        + cid.getSerial());
                        }
                    } catch (DigiDocException ex) {
                        m_logger.error("Error setting type on certid or certval: " + cn);
                    }
                }
            }

        } // for i < sig.countCertIDs()
        if (m_logger.isDebugEnabled())
            m_logger.debug("Sig: " + sig.getId() + " certvals: " + sig.countCertValues());
        for (int i = 0; (sig != null) && (i < sig.countCertValues()); i++) {
            CertValue cval = sig.getCertValue(i);
            if (m_logger.isDebugEnabled())
                m_logger.debug("CertValue: " + cval.getId() + " type: " + cval.getType());
            if (cval.getType() == CertValue.CERTVAL_TYPE_UNKNOWN) {
                String cn = null;
                try {
                    cn = SignedDoc.getCommonName(cval.getCert().getSubjectDN().getName());
                    if (ConvertUtils.isKnownOCSPCert(cn)) {
                        if (m_logger.isDebugEnabled())
                            m_logger.debug("Cert: " + cn + " is OCSP responders cert");
                        cval.setType(CertValue.CERTVAL_TYPE_RESPONDER);
                    }
                    if (ConvertUtils.isKnownTSACert(cn)) {
                        if (m_logger.isDebugEnabled())
                            m_logger.debug("Cert: " + cn + " is TSA cert");
                        cval.setType(CertValue.CERTVAL_TYPE_TSA);
                    }
                } catch (DigiDocException ex) {
                    m_logger.error("Error setting type on certid or certval: " + cn);
                }
            }
        }
    }

    /**
     * End Document handler
     */
    public void endDocument() throws SAXException {

    }

    /*private void debugWriteFile(String name, String data)
    {
        try {
       String str = "C:\\veiko\\JDigiTest1\\" + name;
       System.out.println("Writing debug file: " + str);
       FileOutputStream fos = new FileOutputStream(str);
       fos.write(data.getBytes("UTF-8"));
       fos.close();
        } catch(Exception ex) {
       System.out.println("Error: " + ex);
       ex.printStackTrace(System.out);
        }
    }*/

    /**
     * Start Element handler
     * @param namespaceURI namespace URI
     * @param lName local name
     * @param qName qualified name
     * @param attrs attributes
     */
    public void startElement(String namespaceURI, String lName, String qName, Attributes attrs)
            throws SAXDigiDocException {
        if (m_logger.isDebugEnabled())
            m_logger.debug("Start Element: " + qName + " lname: " + lName + " uri: " + namespaceURI);
        m_tags.push(qName);
        if (qName.equals("SigningTime") || qName.equals("IssuerSerial") || qName.equals("X509SerialNumber")
                || qName.equals("X509IssuerName") || qName.equals("ClaimedRole") || qName.equals("City")
                || qName.equals("StateOrProvince") || qName.equals("CountryName") || qName.equals("PostalCode")
                || qName.equals("SignatureValue") || qName.equals("DigestValue") ||
                //qName.equals("EncapsulatedX509Certificate") ||
                qName.equals("IssuerSerial") || qName.equals("ResponderID") || qName.equals("X509SerialNumber")
                || qName.equals("ProducedAt") || qName.equals("EncapsulatedTimeStamp")
                || qName.equals("EncapsulatedOCSPValue"))
            m_sbCollectItem = new StringBuffer();
        // <X509Certificate>
        // Prepare CertValue object
        if (qName.equals("X509Certificate")) {
            Signature sig = getLastSignature();
            CertValue cval = null;
            try {
                if (m_logger.isDebugEnabled())
                    m_logger.debug("Adding signers cert to: " + sig.getId());
                cval = sig.getOrCreateCertValueOfType(CertValue.CERTVAL_TYPE_SIGNER);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
            m_sbCollectItem = new StringBuffer();
        }
        // <EncapsulatedX509Certificate>
        // Prepare CertValue object and record it's id
        if (qName.equals("EncapsulatedX509Certificate")) {
            Signature sig = getLastSignature();
            String id = null;
            for (int i = 0; i < attrs.getLength(); i++) {
                String key = attrs.getQName(i);
                if (key.equals("Id")) {
                    id = attrs.getValue(i);
                }
            }
            CertValue cval = new CertValue();
            if (id != null) {
                cval.setId(id);
                try {
                    if (id.indexOf("RESPONDER_CERT") != -1)
                        cval.setType(CertValue.CERTVAL_TYPE_RESPONDER);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            if (m_logger.isDebugEnabled())
                m_logger.debug("Adding cval " + cval.getId() + " type: " + cval.getType() + " to: " + sig.getId());
            sig.addCertValue(cval);
            m_sbCollectItem = new StringBuffer();
        }
        //System.out.println("Allocating default buf");
        // the following elements switch collect mode
        // in and out
        // <DataFile>
        if (qName.equals("DataFile")) {
            String ContentType = null, Filename = null, Id = null, MimeType = null, Size = null, DigestType = null,
                    Codepage = null;
            byte[] DigestValue = null;
            ArrayList dfAttrs = new ArrayList();
            for (int i = 0; i < attrs.getLength(); i++) {
                String key = attrs.getQName(i);
                if (key.equals("ContentType")) {
                    ContentType = attrs.getValue(i);
                } else if (key.equals("Filename")) {
                    Filename = attrs.getValue(i);
                } else if (key.equals("Id")) {
                    Id = attrs.getValue(i);
                } else if (key.equals("MimeType")) {
                    MimeType = attrs.getValue(i);
                } else if (key.equals("Size")) {
                    Size = attrs.getValue(i);
                } else if (key.equals("DigestType")) {
                    DigestType = attrs.getValue(i);
                } else if (key.equals("Codepage")) {
                    Codepage = attrs.getValue(i);
                } else if (key.equals("DigestValue")) {
                    DigestValue = Base64Util.decode(attrs.getValue(i));
                } else {
                    try {
                        if (!key.equals("xmlns")) {
                            DataFileAttribute attr = new DataFileAttribute(key, attrs.getValue(i));
                            dfAttrs.add(attr);
                        }
                    } catch (DigiDocException ex) {
                        SAXDigiDocException.handleException(ex);
                    }
                } // else
            } // for
            if (m_nCollectMode == 0) {
                try {
                    DataFile df = new DataFile(Id, ContentType, Filename, MimeType, m_doc);
                    m_dfCacheOutStream = null; // default is don't use cache file
                    if (Size != null)
                        df.setSize(Long.parseLong(Size));
                    if (DigestType != null)
                        df.setDigestType(DigestType);
                    if (DigestValue != null)
                        df.setDigestValue(DigestValue);
                    if (Codepage != null)
                        df.setInitialCodepage(Codepage);
                    for (int i = 0; i < dfAttrs.size(); i++)
                        df.addAttribute((DataFileAttribute) dfAttrs.get(i));
                    // enable caching if requested
                    if (df.schouldUseTempFile()) {
                        File fCache = df.createCacheFile();
                        if (m_logger.isDebugEnabled())
                            m_logger.debug("DF: " + Id + " size: " + df.getSize() + " cache-file: "
                                    + fCache.getAbsolutePath());
                        m_dfCacheOutStream = new FileOutputStream(fCache);
                    }
                    m_doc.addDataFile(df);
                } catch (IOException ex) {
                    SAXDigiDocException.handleException(ex);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            m_nCollectMode++;
            // try to anticipate how much memory we need for collecting this <DataFile>
            try {
                if (Size != null) {
                    int nSize = Integer.parseInt(Size);
                    if (ContentType.equals(DataFile.CONTENT_EMBEDDED)) {
                        nSize += 1024; // just a little bit for whitespace & xml tags
                        m_bCollectDigest = false;
                    }
                    if (ContentType.equals(DataFile.CONTENT_EMBEDDED_BASE64)) {
                        nSize *= 2;
                        m_bCollectDigest = true;
                        System.out.println("Start collecting digest");
                    }
                    //System.out.println("Allocating buf: " + nSize + " Element: "   + qName + " lname: "  + lName + " uri: " + namespaceURI);
                    if (m_dfCacheOutStream == null) // if we use temp files then we don't cache in memory 
                        m_sbCollectChars = new StringBuffer(nSize);
                }
            } catch (Exception ex) {
                m_logger.error("Error: " + ex);
            }
        }

        // <SignedInfo>
        if (qName.equals("SignedInfo")) {
            if (m_nCollectMode == 0) {
                try {
                    if (m_doc.getVersion().equals(SignedDoc.VERSION_1_3)
                            || m_doc.getVersion().equals(SignedDoc.VERSION_1_4))
                        m_xmlnsAttr = null;
                    else
                        m_xmlnsAttr = SignedDoc.xmlns_xmldsig;
                    Signature sig = getLastSignature();
                    SignedInfo si = new SignedInfo(sig);
                    sig.setSignedInfo(si);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            m_nCollectMode++;
            m_sbCollectChars = new StringBuffer(1024);
        }
        // <SignedProperties>
        if (qName.equals("SignedProperties")) {
            String Id = attrs.getValue("Id");
            String Target = attrs.getValue("Target");
            if (m_nCollectMode == 0) {
                try {
                    if (m_doc.getVersion().equals(SignedDoc.VERSION_1_3)
                            || m_doc.getVersion().equals(SignedDoc.VERSION_1_4))
                        m_xmlnsAttr = null;
                    else
                        m_xmlnsAttr = SignedDoc.xmlns_xmldsig;
                    Signature sig = getLastSignature();
                    SignedProperties sp = new SignedProperties(sig);
                    sp.setId(Id);
                    if (Target != null)
                        sp.setTarget(Target);
                    sig.setSignedProperties(sp);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            m_nCollectMode++;
            m_sbCollectChars = new StringBuffer(2048);
        }
        // <Signature>
        if (qName.equals("Signature") && m_nCollectMode == 0) {
            if (m_logger.isDebugEnabled())
                m_logger.debug("Start collecting <Signature>");
            String str1 = attrs.getValue("Id");
            Signature sig = getLastSignature();
            if (sig == null || !sig.getId().equals(str1)) {
                if (m_logger.isDebugEnabled())
                    m_logger.debug("Create signature: " + str1);
                sig = new Signature(m_doc);
                m_doc.addSignature(sig);
            }
            try {
                if (str1 != null)
                    sig.setId(str1);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
            m_sbCollectSignature = new StringBuffer();
        }
        // <SignatureValue>
        if (qName.equals("SignatureValue") && m_nCollectMode == 0) {
            m_strSigValTs = null;
            m_nCollectMode++;
            m_sbCollectChars = new StringBuffer(1024);
        }
        // <SignatureTimeStamp>
        if (qName.equals("SignatureTimeStamp") && m_nCollectMode == 0) {
            m_strSigAndRefsTs = null;
            m_nCollectMode++;
            m_sbCollectChars = new StringBuffer(2048);
        }
        // collect <Signature> data
        if (m_sbCollectSignature != null) {
            m_sbCollectSignature.append("<");
            m_sbCollectSignature.append(qName);
            for (int i = 0; i < attrs.getLength(); i++) {
                m_sbCollectSignature.append(" ");
                m_sbCollectSignature.append(attrs.getQName(i));
                m_sbCollectSignature.append("=\"");
                m_sbCollectSignature.append(attrs.getValue(i));
                m_sbCollectSignature.append("\"");
            }
            m_sbCollectSignature.append(">");
        }
        // if we just switched to collect-mode
        // collect SAX event data to original XML data
        // for <DataFile> we don't collect the begin and
        // end tags unless this an embedded <DataFile>
        if (m_nCollectMode > 0 || m_sbCollectChars != null) {
            StringBuffer sb = new StringBuffer();
            sb.append("<");
            sb.append(qName);
            for (int i = 0; i < attrs.getLength(); i++) {
                if (attrs.getQName(i).equals("xmlns"))
                    m_xmlnsAttr = null; // allready have it from document
                sb.append(" ");
                sb.append(attrs.getQName(i));
                sb.append("=\"");
                sb.append(attrs.getValue(i));
                sb.append("\"");
            }
            if (m_xmlnsAttr != null) {
                sb.append(" xmlns=\"" + m_xmlnsAttr + "\"");
                m_xmlnsAttr = null;
            }
            sb.append(">");
            //canonicalize & calculate digest over DataFile begin-tag without content
            if (qName.equals("DataFile") && m_nCollectMode == 1) {
                String strCan = sb.toString() + "</DataFile>";
                strCan = canonicalizeXml(strCan);
                strCan = strCan.substring(0, strCan.length() - 11);
                if (m_logger.isDebugEnabled())
                    m_logger.debug("Canonicalized: \'" + strCan + "\'");
                try {
                    updateDigest(ConvertUtils.str2data(strCan));
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            } // we don't collect <DataFile> begin and end - tags and we don't collect if we use temp files 
            else {
                if (m_sbCollectChars != null)
                    m_sbCollectChars.append(sb.toString());
                try {
                    if (m_dfCacheOutStream != null)
                        m_dfCacheOutStream.write(ConvertUtils.str2data(sb.toString()));
                } catch (IOException ex) {
                    SAXDigiDocException.handleException(ex);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
        }

        // the following stuff is used also on level 1
        // because it can be part of SignedInfo or SignedProperties
        if (m_nCollectMode == 1) {
            // <CanonicalizationMethod>
            if (qName.equals("CanonicalizationMethod")) {
                String Algorithm = attrs.getValue("Algorithm");
                try {
                    Signature sig = getLastSignature();
                    SignedInfo si = sig.getSignedInfo();
                    si.setCanonicalizationMethod(Algorithm);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <SignatureMethod>
            if (qName.equals("SignatureMethod")) {
                String Algorithm = attrs.getValue("Algorithm");
                try {
                    Signature sig = getLastSignature();
                    SignedInfo si = sig.getSignedInfo();
                    si.setSignatureMethod(Algorithm);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <Reference>
            if (qName.equals("Reference")) {
                String URI = attrs.getValue("URI");
                try {
                    Signature sig = getLastSignature();
                    SignedInfo si = sig.getSignedInfo();
                    Reference ref = new Reference(si);
                    ref.setUri(URI);
                    si.addReference(ref);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <Transform>
            if (qName.equals("Transform")) {
                String Algorithm = attrs.getValue("Algorithm");
                try {
                    if (m_tags.search("Reference") != -1) {
                        Signature sig = getLastSignature();
                        SignedInfo si = sig.getSignedInfo();
                        Reference ref = si.getLastReference();
                        ref.setTransformAlgorithm(Algorithm);
                    }
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <SignatureProductionPlace>
            if (qName.equals("SignatureProductionPlace")) {
                try {
                    Signature sig = getLastSignature();
                    SignedProperties sp = sig.getSignedProperties();
                    SignatureProductionPlace spp = new SignatureProductionPlace();
                    sp.setSignatureProductionPlace(spp);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }

        }
        // the following is collected anyway independent of collect mode
        // <SignatureValue>
        if (qName.equals("SignatureValue")) {
            String Id = attrs.getValue("Id");
            try {
                SignatureValue sv = new SignatureValue();
                // VS: 2.2.24 - fix to allowe SignatureValue without Id atribute
                if (Id != null)
                    sv.setId(Id);
                Signature sig = getLastSignature();
                sig.setSignatureValue(sv);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <DigestMethod>
        if (qName.equals("DigestMethod")) {
            String Algorithm = attrs.getValue("Algorithm");
            try {
                if (m_tags.search("Reference") != -1) {
                    Signature sig = getLastSignature();
                    SignedInfo si = sig.getSignedInfo();
                    Reference ref = si.getLastReference();
                    ref.setDigestAlgorithm(Algorithm);
                } else if (m_tags.search("SigningCertificate") != -1) {
                    Signature sig = getLastSignature();
                    CertID cid = sig.getOrCreateCertIdOfType(CertID.CERTID_TYPE_SIGNER);
                    cid.setDigestAlgorithm(Algorithm);
                } else if (m_tags.search("CompleteCertificateRefs") != -1) {
                    Signature sig = getLastSignature();
                    CertID cid = sig.getLastCertId(); // initially set to unknown type !
                    cid.setDigestAlgorithm(Algorithm);
                } else if (m_tags.search("CompleteRevocationRefs") != -1) {
                    Signature sig = getLastSignature();
                    UnsignedProperties up = sig.getUnsignedProperties();
                    CompleteRevocationRefs rrefs = up.getCompleteRevocationRefs();
                    rrefs.setDigestAlgorithm(Algorithm);
                }
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <Cert>
        if (qName.equals("Cert")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                if (m_tags.search("SigningCertificate") != -1) {
                    CertID cid = sig.getOrCreateCertIdOfType(CertID.CERTID_TYPE_SIGNER);
                    if (id != null)
                        cid.setId(id);
                }
                if (m_tags.search("CompleteCertificateRefs") != -1) {
                    CertID cid = new CertID();
                    if (id != null)
                        cid.setId(id);
                    sig.addCertID(cid);
                }
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <AllDataObjectsTimeStamp>
        if (qName.equals("AllDataObjectsTimeStamp")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = new TimestampInfo(id, TimestampInfo.TIMESTAMP_TYPE_ALL_DATA_OBJECTS);
                sig.addTimestampInfo(ts);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <IndividualDataObjectsTimeStamp>
        if (qName.equals("IndividualDataObjectsTimeStamp")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = new TimestampInfo(id, TimestampInfo.TIMESTAMP_TYPE_INDIVIDUAL_DATA_OBJECTS);
                sig.addTimestampInfo(ts);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <SignatureTimeStamp>
        if (qName.equals("SignatureTimeStamp")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = new TimestampInfo(id, TimestampInfo.TIMESTAMP_TYPE_SIGNATURE);
                sig.addTimestampInfo(ts);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <SigAndRefsTimeStamp>
        if (qName.equals("SigAndRefsTimeStamp")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = new TimestampInfo(id, TimestampInfo.TIMESTAMP_TYPE_SIG_AND_REFS);
                sig.addTimestampInfo(ts);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <RefsOnlyTimeStamp>
        if (qName.equals("RefsOnlyTimeStamp")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = new TimestampInfo(id, TimestampInfo.TIMESTAMP_TYPE_REFS_ONLY);
                sig.addTimestampInfo(ts);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <ArchiveTimeStamp>
        if (qName.equals("ArchiveTimeStamp")) {
            String id = attrs.getValue("Id");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = new TimestampInfo(id, TimestampInfo.TIMESTAMP_TYPE_ARCHIVE);
                sig.addTimestampInfo(ts);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <Include>
        if (qName.equals("Include")) {
            String uri = attrs.getValue("URI");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = sig.getLastTimestampInfo();
                IncludeInfo inc = new IncludeInfo(uri);
                ts.addIncludeInfo(inc);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <CompleteCertificateRefs>
        if (qName.equals("CompleteCertificateRefs")) {
            String Target = attrs.getValue("Target");
            try {
                Signature sig = getLastSignature();
                UnsignedProperties up = sig.getUnsignedProperties();
                CompleteCertificateRefs crefs = new CompleteCertificateRefs();
                up.setCompleteCertificateRefs(crefs);
                crefs.setUnsignedProperties(up);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <CompleteRevocationRefs>
        if (qName.equals("CompleteRevocationRefs")) {
            try {
                Signature sig = getLastSignature();
                UnsignedProperties up = sig.getUnsignedProperties();
                CompleteRevocationRefs rrefs = new CompleteRevocationRefs();
                up.setCompleteRevocationRefs(rrefs);
                rrefs.setUnsignedProperties(up);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // <OCSPIdentifier>
        if (qName.equals("OCSPIdentifier")) {
            String URI = attrs.getValue("URI");
            try {
                Signature sig = getLastSignature();
                UnsignedProperties up = sig.getUnsignedProperties();
                CompleteRevocationRefs rrefs = up.getCompleteRevocationRefs();
                rrefs.setUri(URI);
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }

        // the following stuff is ignored in collect mode
        // because it can only be the content of a higher element
        if (m_nCollectMode == 0) {
            // <SignedDoc>
            if (qName.equals("SignedDoc")) {
                String format = null, version = null;
                for (int i = 0; i < attrs.getLength(); i++) {
                    String key = attrs.getQName(i);
                    if (key.equals("format"))
                        format = attrs.getValue(i);
                    if (key.equals("version"))
                        version = attrs.getValue(i);
                }
                try {
                    m_doc = new SignedDoc(format, version);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <Signature>
            /*if (qName.equals("Signature")) {
               String Id = attrs.getValue("Id");
               try {
                  System.out.println("Creating signature: " + Id);
                  Signature sig = new Signature(m_doc);
                  if(Id != null)
              sig.setId(Id);
                  m_doc.addSignature(sig);
               } catch (DigiDocException ex) {
                  SAXDigiDocException.handleException(ex);
               }
            }*/
            // <KeyInfo>
            if (qName.equals("KeyInfo")) {
                try {
                    KeyInfo ki = new KeyInfo();
                    Signature sig = getLastSignature();
                    sig.setKeyInfo(ki);
                    ki.setSignature(sig);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <UnsignedProperties>
            if (qName.equals("UnsignedProperties")) {
                String Target = attrs.getValue("Target");
                try {
                    Signature sig = getLastSignature();
                    UnsignedProperties up = new UnsignedProperties(sig);
                    sig.setUnsignedProperties(up);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // <EncapsulatedOCSPValue>
            if (qName.equals("EncapsulatedOCSPValue")) {
                String Id = attrs.getValue("Id");
                try {
                    Signature sig = getLastSignature();
                    UnsignedProperties up = sig.getUnsignedProperties();
                    Notary not = new Notary();
                    not.setId(Id);
                    up.setNotary(not);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
        } // if(m_nCollectMode == 0)
    }

    /**
     * End Element handler
     * @param namespaceURI namespace URI
     * @param lName local name
     * @param qName qualified name
     */
    public void endElement(String namespaceURI, String sName, String qName) throws SAXException {
        if (m_logger.isDebugEnabled())
            m_logger.debug("End Element: " + qName + " collect: " + m_nCollectMode);
        // remove last tag from stack
        String currTag = (String) m_tags.pop();
        // collect SAX event data to original XML data
        // for <DataFile> we don't collect the begin and
        // end tags unless this an embedded <DataFile>
        StringBuffer sb = null;
        if (m_nCollectMode > 0 && (!qName.equals("DataFile") || m_nCollectMode > 1)) {
            sb = new StringBuffer();
            sb.append("</");
            sb.append(qName);
            sb.append(">");
        }
        if (m_sbCollectSignature != null) {
            m_sbCollectSignature.append("</");
            m_sbCollectSignature.append(qName);
            m_sbCollectSignature.append(">");
        }
        // if we do cache in mem
        if (m_sbCollectChars != null && sb != null)
            m_sbCollectChars.append(sb.toString());

        // </DataFile>
        if (qName.equals("DataFile")) {
            m_nCollectMode--;
            if (m_nCollectMode == 0) {
                // close DataFile cache if necessary
                try {
                    if (m_dfCacheOutStream != null) {
                        if (sb != null)
                            m_dfCacheOutStream.write(ConvertUtils.str2data(sb.toString()));
                        m_dfCacheOutStream.close();
                        m_dfCacheOutStream = null;
                    }
                } catch (IOException ex) {
                    SAXDigiDocException.handleException(ex);
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }

                DataFile df = m_doc.getLastDataFile();
                System.out.println("End DF: " + df.getId() + " collect: " + m_nCollectMode);
                //debugWriteFile("DF-" + df.getId() + ".txt", m_sbCollectChars.toString()); 
                if (df.getContentType().equals(DataFile.CONTENT_EMBEDDED)) {
                    try {
                        System.out.println("Set body: " + df.getId() + " -> " + m_sbCollectChars.toString());
                        if (df.getDfCacheFile() == null)
                            df.setBody(ConvertUtils.str2data(sb.toString(), df.getInitialCodepage()));
                        // canonicalize and calculate digest of body
                        String str1 = sb.toString();
                        m_sbCollectChars = null;
                        // check for whitespace before first tag of body
                        int idx1 = 0;
                        while (Character.isWhitespace(str1.charAt(idx1)))
                            idx1++;
                        //idx1 = str1.indexOf('<');
                        String str2 = null;
                        if (idx1 > 0) {
                            str2 = str1.substring(0, idx1);
                            //System.out.println("prefix: \"" + str2 + "\"");
                            updateDigest(str2.getBytes());
                            str2 = null;
                            str1 = str1.substring(idx1);
                        }
                        // check for whitespace after the last xml tag of body
                        idx1 = str1.length() - 1;
                        while (Character.isWhitespace(str1.charAt(idx1)))
                            idx1--;
                        //idx1 = str1.lastIndexOf('>');
                        if (idx1 < str1.length() - 1) {
                            str2 = str1.substring(idx1 + 1);
                            //System.out.println("suffix: \"" + str2 + "\"");
                            str1 = str1.substring(0, idx1 + 1);
                        }
                        //System.out.println("Body: \"" + str1 + "\"");
                        //debugWriteFile("DF-" + df.getId() + "-body.txt", str1); 
                        // canonicalized body
                        String str3 = null;
                        if (str1.charAt(0) == '<')
                            str3 = canonicalizeXml(str1);
                        else
                            str3 = str1;
                        //System.out.println("Canonical: \"" + str3 + "\"");
                        //debugWriteFile("DF-" + df.getId() + "-can.txt", str3); 
                        updateDigest(ConvertUtils.str2data(str3));

                        if (str2 != null) {
                            updateDigest(ConvertUtils.str2data(str2));
                            str2 = null;
                        }
                        //calc digest over end tag
                        updateDigest(ConvertUtils.str2data("</DataFile>"));
                        df.setDigest(getDigest());
                        System.out.println("Digest: " + df.getId() + " - " + Base64Util.encode(df.getDigest()));
                        m_sbCollectChars = null; // stop collecting
                        //} catch(java.io.UnsupportedEncodingException ex) {
                        //SAXDigiDocException.handleException(ex);
                    } catch (DigiDocException ex) {
                        SAXDigiDocException.handleException(ex);
                    }
                }
                if (df.getContentType().equals(DataFile.CONTENT_EMBEDDED_BASE64)) {
                    try {
                        // calc digest over end tag
                        updateDigest("</DataFile>".getBytes());
                        //System.out.println("Set digest: " + df.getId());
                        df.setDigest(getDigest());
                        System.out.println("Digest: " + df.getId() + " - " + Base64Util.encode(df.getDigest()));
                        //System.out.println("Set body: " + df.getId());
                        if (df.getDfCacheFile() == null && sb != null)
                            df.setBody(ConvertUtils.str2data(sb.toString(), df.getInitialCodepage()));
                        m_sbCollectChars = null; // stop collecting
                    } catch (DigiDocException ex) {
                        SAXDigiDocException.handleException(ex);
                    }
                    // this would throw away whitespace so calculate digest before it
                    //df.setBody(Base64Util.decode(m_sbCollectChars.toString()));
                }
                //System.out.println("Done: " + df.getId());
                m_bCollectDigest = false;
            }
        }
        // </SignedInfo>
        if (qName.equals("SignedInfo")) {
            if (m_nCollectMode > 0)
                m_nCollectMode--;
            // calculate digest over the original
            // XML form of SignedInfo block and save it
            try {
                Signature sig = getLastSignature();
                SignedInfo si = sig.getSignedInfo();
                //debugWriteFile("SigInfo1.xml", m_sbCollectChars.toString());
                CanonicalizationFactory canFac = ConfigManager.instance().getCanonicalizationFactory();
                byte[] bCanSI = canFac.canonicalize(ConvertUtils.str2data(m_sbCollectChars.toString(), "UTF-8"),
                        SignedDoc.CANONICALIZATION_METHOD_20010315);
                si.setOrigDigest(SignedDoc.digest(bCanSI));
                m_sbCollectChars = null; // stop collecting
                //debugWriteFile("SigInfo2.xml", si.toString());
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }

        }
        // </SignedProperties>
        if (qName.equals("SignedProperties")) {
            if (m_nCollectMode > 0)
                m_nCollectMode--;
            // calculate digest over the original
            // XML form of SignedInfo block and save it
            //debugWriteFile("SigProps-orig.xml", m_sbCollectChars.toString());
            try {
                Signature sig = getLastSignature();
                SignedProperties sp = sig.getSignedProperties();
                String sigProp = m_sbCollectChars.toString();
                //debugWriteFile("SigProp1.xml", sigProp);
                //System.out.println("SigProp1: " + sigProp.length() 
                //    + " digest: " + Base64Util.encode(SignedDoc.digest(sigProp.getBytes())));
                CanonicalizationFactory canFac = ConfigManager.instance().getCanonicalizationFactory();
                byte[] bCanProp = canFac.canonicalize(ConvertUtils.str2data(sigProp, "UTF-8"),
                        SignedDoc.CANONICALIZATION_METHOD_20010315);
                //debugWriteFile("SigProp2.xml", new String(bCanProp));
                sp.setOrigDigest(SignedDoc.digest(bCanProp));
                //System.out.println("Digest: " + Base64Util.encode(SignedDoc.digest(bCanProp)));
                //System.out.println("SigProp2: " + sp.toString());
                m_sbCollectChars = null; // stop collecting
                CertID cid = sig.getCertIdOfType(CertID.CERTID_TYPE_SIGNER);
                if (cid != null) {
                    /*System.out.println("CID: " + cid.getId() + " serial: " + cid.getSerial() +
                          " alg: " + cid.getDigestAlgorithm() + " diglen: " +
                          ((cid.getDigestValue() == null) ? 0 : cid.getDigestValue().length));*/
                    if (cid.getId() != null)
                        sp.setCertId(cid.getId());
                    else if (!sig.getSignedDoc().getVersion().equals(SignedDoc.VERSION_1_3))
                        sp.setCertId(sig.getId() + "-CERTINFO");
                    sp.setCertSerial(cid.getSerial());
                    sp.setCertDigestAlgorithm(cid.getDigestAlgorithm());
                    if (cid.getDigestValue() != null) {
                        sp.setCertDigestValue(cid.getDigestValue());
                    }
                }
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </SignatureValue>
        if (qName.equals("SignatureValue")) {
            if (m_nCollectMode > 0)
                m_nCollectMode--;
            m_strSigValTs = m_sbCollectChars.toString();
            //System.out.println("SigValTS mode: " + m_nCollectMode + "\n---\n" + m_strSigValTs + "\n---\n");         
            m_sbCollectChars = null; // stop collecting            
        }
        // </CompleteRevocationRefs>
        if (qName.equals("CompleteRevocationRefs")) {
            if (m_nCollectMode > 0)
                m_nCollectMode--;
            if (m_sbCollectChars != null)
                m_strSigAndRefsTs = m_strSigValTs + m_sbCollectChars.toString();
            //System.out.println("SigAndRefsTs mode: " + m_nCollectMode + "\n---\n" + m_strSigAndRefsTs + "\n---\n");
            m_sbCollectChars = null; // stop collecting         
        }
        // </Signature>
        if (qName.equals("Signature")) {
            if (m_nCollectMode == 0) {
                if (m_logger.isDebugEnabled())
                    m_logger.debug("End collecting <Signature>");
                try {
                    Signature sig = getLastSignature();
                    if (m_logger.isDebugEnabled())
                        m_logger.debug("Set sig content:\n---\n" + m_sbCollectSignature.toString() + "\n---\n");
                    if (m_sbCollectSignature != null) {
                        sig.setOrigContent(ConvertUtils.str2data(m_sbCollectSignature.toString(), "UTF-8"));
                        if (m_logger.isDebugEnabled())
                            m_logger.debug("SIG orig content set: " + sig.getId() + " len: "
                                    + ((sig.getOrigContent() == null) ? 0 : sig.getOrigContent().length));
                        //debugWriteFile("SIG-" + sig.getId() + ".txt", m_sbCollectSignature.toString()); 
                        m_sbCollectSignature = null; // reset collecting
                    }
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
        }
        // </SignatureTimeStamp>
        if (qName.equals("SignatureTimeStamp")) {
            if (m_logger.isDebugEnabled())
                m_logger.debug("End collecting <SignatureTimeStamp>");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = sig.getTimestampInfoOfType(TimestampInfo.TIMESTAMP_TYPE_SIGNATURE);
                if (ts != null && m_strSigValTs != null) {
                    //System.out.println("SigValTS \n---\n" + m_strSigValTs + "\n---\n");
                    CanonicalizationFactory canFac = ConfigManager.instance().getCanonicalizationFactory();
                    byte[] bCanXml = canFac.canonicalize(ConvertUtils.str2data(m_strSigValTs, "UTF-8"),
                            SignedDoc.CANONICALIZATION_METHOD_20010315);
                    byte[] hash = SignedDoc.digest(bCanXml);
                    //System.out.println("SigValTS hash: " + Base64Util.encode(hash));
                    //debugWriteFile("SigProp2.xml", new String(bCanProp));
                    ts.setHash(hash);
                }
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </SigAndRefsTimeStamp>
        if (qName.equals("SigAndRefsTimeStamp")) {
            if (m_logger.isDebugEnabled())
                m_logger.debug("End collecting <SigAndRefsTimeStamp>");
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = sig.getTimestampInfoOfType(TimestampInfo.TIMESTAMP_TYPE_SIG_AND_REFS);
                if (ts != null && m_strSigAndRefsTs != null) {
                    String canXml = "<a>" + m_strSigAndRefsTs + "</a>";
                    //System.out.println("SigAndRefsTS \n---\n" + m_strSigAndRefsTs + "\n---\n");
                    CanonicalizationFactory canFac = ConfigManager.instance().getCanonicalizationFactory();
                    byte[] bCanXml = canFac.canonicalize(ConvertUtils.str2data(canXml, "UTF-8"),
                            SignedDoc.CANONICALIZATION_METHOD_20010315);
                    canXml = new String(bCanXml, "UTF-8");
                    canXml = canXml.substring(3, canXml.length() - 4);
                    //System.out.println("canonical \n---\n" + canXml + "\n---\n");
                    //debugWriteFile("SigProp2.xml", new String(bCanProp));
                    byte[] hash = SignedDoc.digest(ConvertUtils.str2data(canXml, "UTF-8"));
                    //System.out.println("SigAndRefsTS hash: " + Base64Util.encode(hash));
                    ts.setHash(hash);
                }
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            } catch (Exception ex) {
                //SAXDigiDocException.handleException(ex);
            }
        }
        // the following stuff is used also in
        // collect mode level 1 because it can be part 
        // of SignedInfo or SignedProperties
        if (m_nCollectMode == 1) {
            // </SigningTime>
            if (qName.equals("SigningTime")) {
                try {
                    Signature sig = getLastSignature();
                    SignedProperties sp = sig.getSignedProperties();
                    sp.setSigningTime(ConvertUtils.string2date(m_sbCollectItem.toString(), m_doc));
                    m_sbCollectItem = null; // stop collecting
                } catch (DigiDocException ex) {
                    SAXDigiDocException.handleException(ex);
                }
            }
            // </ClaimedRole>
            if (qName.equals("ClaimedRole")) {
                Signature sig = getLastSignature();
                SignedProperties sp = sig.getSignedProperties();
                sp.addClaimedRole(m_sbCollectItem.toString());
                m_sbCollectItem = null; // stop collecting
            }
            // </City>
            if (qName.equals("City")) {
                Signature sig = getLastSignature();
                SignedProperties sp = sig.getSignedProperties();
                SignatureProductionPlace spp = sp.getSignatureProductionPlace();
                spp.setCity(m_sbCollectItem.toString());
                m_sbCollectItem = null; // stop collecting
            }
            // </StateOrProvince>
            if (qName.equals("StateOrProvince")) {
                Signature sig = getLastSignature();
                SignedProperties sp = sig.getSignedProperties();
                SignatureProductionPlace spp = sp.getSignatureProductionPlace();
                spp.setStateOrProvince(m_sbCollectItem.toString());
                m_sbCollectItem = null; // stop collecting
            }
            // </CountryName>
            if (qName.equals("CountryName")) {
                Signature sig = getLastSignature();
                SignedProperties sp = sig.getSignedProperties();
                SignatureProductionPlace spp = sp.getSignatureProductionPlace();
                spp.setCountryName(m_sbCollectItem.toString());
                m_sbCollectItem = null; // stop collecting
            }
            // </PostalCode>
            if (qName.equals("PostalCode")) {
                Signature sig = getLastSignature();
                SignedProperties sp = sig.getSignedProperties();
                SignatureProductionPlace spp = sp.getSignatureProductionPlace();
                spp.setPostalCode(m_sbCollectItem.toString());
                m_sbCollectItem = null; // stop collecting
            }

        } // level 1   
          // the following is collected on any level
          // </DigestValue>
        if (qName.equals("DigestValue")) {
            try {
                //System.out.println("DIGEST: " + (m_sbCollectItem != null ? m_sbCollectItem.toString() : "NULL"));
                if (m_tags.search("Reference") != -1) {
                    Signature sig = getLastSignature();
                    SignedInfo si = sig.getSignedInfo();
                    Reference ref = si.getLastReference();
                    ref.setDigestValue(Base64Util.decode(m_sbCollectItem.toString()));
                    m_sbCollectItem = null; // stop collecting
                } else if (m_tags.search("SigningCertificate") != -1) {
                    Signature sig = getLastSignature();
                    SignedProperties sp = sig.getSignedProperties();
                    sp.setCertDigestValue(Base64Util.decode(m_sbCollectItem.toString()));
                    CertID cid = sig.getCertIdOfType(CertID.CERTID_TYPE_SIGNER);
                    if (cid != null)
                        cid.setDigestValue(Base64Util.decode(m_sbCollectItem.toString()));
                    m_sbCollectItem = null; // stop collecting
                } else if (m_tags.search("CompleteCertificateRefs") != -1) {
                    Signature sig = getLastSignature();
                    UnsignedProperties up = sig.getUnsignedProperties();
                    CompleteCertificateRefs crefs = up.getCompleteCertificateRefs();
                    CertID cid = crefs.getLastCertId();
                    if (cid != null)
                        cid.setDigestValue(Base64Util.decode(m_sbCollectItem.toString()));
                    //System.out.println("CertID: " + cid.getId() + " digest: " + m_sbCollectItem.toString());
                    m_sbCollectItem = null; // stop collecting
                } else if (m_tags.search("CompleteRevocationRefs") != -1) {
                    Signature sig = getLastSignature();
                    UnsignedProperties up = sig.getUnsignedProperties();
                    CompleteRevocationRefs rrefs = up.getCompleteRevocationRefs();
                    rrefs.setDigestValue(Base64Util.decode(m_sbCollectItem.toString()));
                    m_sbCollectItem = null; // stop collecting
                }
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </IssuerSerial>
        if (qName.equals("IssuerSerial") && !m_doc.getVersion().equals(SignedDoc.VERSION_1_3)
                && !m_doc.getVersion().equals(SignedDoc.VERSION_1_4)) {
            try {
                Signature sig = getLastSignature();
                CertID cid = sig.getLastCertId();
                if (cid != null)
                    cid.setSerial(ConvertUtils.string2bigint(m_sbCollectItem.toString()));
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </X509SerialNumber>
        if (qName.equals("X509SerialNumber") && (m_doc.getVersion().equals(SignedDoc.VERSION_1_3)
                || m_doc.getVersion().equals(SignedDoc.VERSION_1_4))) {
            try {
                Signature sig = getLastSignature();
                CertID cid = sig.getLastCertId();
                if (cid != null)
                    cid.setSerial(ConvertUtils.string2bigint(m_sbCollectItem.toString()));
                //System.out.println("X509SerialNumber: " + cid.getSerial() + " type: " + cid.getType());
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </X509IssuerName>
        if (qName.equals("X509IssuerName") && (m_doc.getVersion().equals(SignedDoc.VERSION_1_3)
                || m_doc.getVersion().equals(SignedDoc.VERSION_1_4))) {
            try {
                Signature sig = getLastSignature();
                CertID cid = sig.getLastCertId();
                String s = m_sbCollectItem.toString();
                if (cid != null)
                    cid.setIssuer(s);
                //System.out.println("X509IssuerName: " + s + " type: " + cid.getType() + " nr: " + cid.getSerial());
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        //</EncapsulatedTimeStamp>
        if (qName.equals("EncapsulatedTimeStamp")) {
            try {
                Signature sig = getLastSignature();
                TimestampInfo ts = sig.getLastTimestampInfo();
                try {
                    //System.out.println("\n--TS_RESP--\n" + m_sbCollectItem.toString() + "\n--TS_RESP--\n");
                    ts.setTimeStampResponse(new TimeStampResponse(Base64Util.decode(m_sbCollectItem.toString())));
                    //ts.setTimeStampToken(new TimeStampToken(new CMSSignedData(Base64Util.decode(m_sbCollectItem.toString()))));
                } catch (TSPException ex) {
                    throw new DigiDocException(DigiDocException.ERR_TIMESTAMP_RESP, "Invalid timestamp response",
                            ex);
                } catch (IOException ex) {
                    throw new DigiDocException(DigiDocException.ERR_TIMESTAMP_RESP, "Invalid timestamp response",
                            ex);
                }
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </ResponderID>
        if (qName.equals("ResponderID")) {
            try {
                Signature sig = getLastSignature();
                UnsignedProperties up = sig.getUnsignedProperties();
                CompleteRevocationRefs rrefs = up.getCompleteRevocationRefs();
                rrefs.setResponderId(m_sbCollectItem.toString());
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </ProducedAt>
        if (qName.equals("ProducedAt")) {
            try {
                Signature sig = getLastSignature();
                UnsignedProperties up = sig.getUnsignedProperties();
                CompleteRevocationRefs rrefs = up.getCompleteRevocationRefs();
                rrefs.setProducedAt(ConvertUtils.string2date(m_sbCollectItem.toString(), m_doc));
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }

        // the following stuff is ignored in collect mode
        // because it can only be the content of a higher element
        //if (m_nCollectMode == 0) {
        // </SignatureValue>
        if (qName.equals("SignatureValue")) {
            try {
                Signature sig = getLastSignature();
                SignatureValue sv = sig.getSignatureValue();
                //debugWriteFile("SigVal.txt", m_sbCollectItem.toString());
                //System.out.println("SIGVAL mode: " + m_nCollectMode + ":\n--\n" + (m_sbCollectItem != null ? m_sbCollectItem.toString() : "NULL"));
                sv.setValue(Base64Util.decode(m_sbCollectItem.toString().trim()));
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </X509Certificate>
        if (qName.equals("X509Certificate")) {
            try {
                Signature sig = getLastSignature();
                CertValue cval = sig.getLastCertValue();
                cval.setCert(SignedDoc.readCertificate(Base64Util.decode(m_sbCollectItem.toString())));
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </EncapsulatedX509Certificate>
        if (qName.equals("EncapsulatedX509Certificate")) {
            try {
                Signature sig = getLastSignature();
                CertValue cval = sig.getLastCertValue();
                cval.setCert(SignedDoc.readCertificate(Base64Util.decode(m_sbCollectItem.toString())));
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
        // </EncapsulatedOCSPValue>
        if (qName.equals("EncapsulatedOCSPValue")) {
            try {
                Signature sig = getLastSignature();
                // first we have to find correct certid and certvalue types
                findCertIDandCertValueTypes(sig);
                UnsignedProperties up = sig.getUnsignedProperties();
                Notary not = up.getNotary();
                not.setOcspResponseData(Base64Util.decode(m_sbCollectItem.toString()));
                NotaryFactory notFac = ConfigManager.instance().getNotaryFactory();
                notFac.parseAndVerifyResponse(sig, not);
                // in 1.1 we had bad OCPS digest
                if (m_doc.getVersion().equals(SignedDoc.VERSION_1_1)) {
                    CompleteRevocationRefs rrefs = up.getCompleteRevocationRefs();
                    rrefs.setDigestValue(SignedDoc.digest(not.getOcspResponseData()));
                }
                m_sbCollectItem = null; // stop collecting
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }

        //} // if(m_nCollectMode == 0)
    }

    /**
     * SAX characters event handler
     * @param buf received bytes array
     * @param offset offset to the array
     * @param len length of data
     */
    public void characters(char buf[], int offset, int len) throws SAXException {
        String s = new String(buf, offset, len);
        //System.out.println("Chars: " + s);
        // just collect the data since it could
        // be on many lines and be processed in many events
        if (s != null) {
            if (m_sbCollectItem != null)
                m_sbCollectItem.append(s);
            if (m_sbCollectChars != null)
                m_sbCollectChars.append(s);
            if (m_sbCollectSignature != null)
                m_sbCollectSignature.append(s);
            if (m_digest != null && m_bCollectDigest)
                updateDigest(s.getBytes());
            try {
                if (m_dfCacheOutStream != null)
                    m_dfCacheOutStream.write(ConvertUtils.str2data(s));
            } catch (DigiDocException ex) {
                SAXDigiDocException.handleException(ex);
            } catch (IOException ex) {
                SAXDigiDocException.handleException(ex);
            }
        }
    }

}