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

ee.sk.xmlenc.EncryptedData Maven / Gradle / Ivy

/*
 * EncryptedData.java
 * PROJECT: JDigiDoc
 * DESCRIPTION: some property of an encrypted data object 
 * 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.xmlenc;

import ee.sk.digidoc.Base64Util;
import ee.sk.digidoc.DataFile;
import ee.sk.digidoc.DigiDocException;
import ee.sk.digidoc.SignedDoc;
import ee.sk.digidoc.factory.SignatureFactory;
import ee.sk.digidoc.factory.Pkcs12SignatureFactory;
import ee.sk.utils.ConfigManager;
import ee.sk.utils.ConvertUtils;
import ee.sk.xmlenc.factory.EncryptedStreamSAXParser;

import org.apache.log4j.Logger;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import java.security.cert.X509Certificate;

/**
 * Contains the data of an  object
 * @author Veiko Sinivee
 * @version 1.0
 */
public class EncryptedData implements Serializable
{
	private static final long serialVersionUID = 1L;
	/**
	 * Id atribute value (optional)
	 */
	private String m_id;
	/**
	 * Type atribute value (optional)
	 */
	private String m_type;
	/**
	 * MimeType atribute value (optional)
	 */
	private String m_mimeType;
	/**
	 * xmlns atribute value. Must be http://www.w3.org/2001/04/xmlenc#
	 */
	private String m_xmlns;
	/**
	 *  sublements atribute Algorithm value (required)
	 */
	private String m_encryptionMethod;
	/**
	 * payload data (encrypted or not encrypted)
	 */
	private byte[] m_data;
	/**
	 * status of data
	 */
	private int m_nDataStatus;
	/**
	 * array of  objects
	 */
	private ArrayList m_arrEncryptedKeys;
	/**
	 * array of  objects
	 */
	private EncryptionProperties m_encProperties;
	/**
	 * log4j logger
	 */
	private static Logger m_logger = Logger.getLogger(EncryptedData.class);
	/**
	 * transport key
	 */
	private transient SecretKey m_transportKey;

	/**
	 * use this value for Type atribute if you encrypt a digidoc
	 */
	public static final String DENC_ENCDATA_TYPE_DDOC = "http://www.sk.ee/DigiDoc/v1.3.0/digidoc.xsd";
	
	/**
	 * mime type for xml data
	 */
	public static final String DENC_ENCDATA_MIME_XML = "text/xml";
	/**
	 * the library will set mime type to this value if it packs data
	 */
	public static final String DENC_ENCDATA_MIME_ZLIB = "http://www.isi.edu/in-noes/iana/assignments/media-types/application/zip";
	/**
	 * the only acceptable encryption method for EncryptedData for now
	 */
	public static final String DENC_ENC_METHOD_AES128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc";
	/**
	 * the only acceptable encryption method for EncryptedKey for now
	 */
	public static final String DENC_ENC_METHOD_RSA1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5";
	/**
	 * the only acceptable encryption method for EncryptedKey for now
	 */
	public static final String DENC_ENC_METHOD_RSA1_5_BUGGY = "http://www.w3.org/2001/04/xmlenc#rsa-1-5";
	/**
	 * the only acceptable namespace for EncryptedData for now
	 */
	public static final String DENC_XMLNS_XMLENC = "http://www.w3.org/2001/04/xmlenc#";
	/**
	 * we don't use this
	 */
	public static final String DENC_XMLNS_XMLENC_ELEMENT = "http://www.w3.org/2001/04/xmlenc#Element";
	/**
	 * we don't use this
	 */
	public static final String DENC_XMLNS_XMLENC_CONTENT = "http://www.w3.org/2001/04/xmlenc#Content";
	/**
	 * we don't use this
	 */
	public static final String DENC_XMLNS_XMLENC_ENCPROP = "http://www.w3.org/2001/04/xmlenc#EncryptionProperties";
	/**
	 * we use this for  and it's sublements
	 */
	public static final String DENC_XMLNS_XMLDSIG = "http://www.w3.org/2000/09/xmldsig#";

	/**
	 * when there is no data not encrypted, not unencrypted
	 */
	public static final int DENC_DATA_STATUS_UNINITIALIZED = 0;
	/**
	 * unencrypted and not compressed data
	 */
	public static final int DENC_DATA_STATUS_UNENCRYPTED_AND_NOT_COMPRESSED = 1;
	/**
	 * unencrypted and compressed data
	 */
	public static final int DENC_DATA_STATUS_UNENCRYPTED_AND_COMPRESSED = 2;
	/**
	 * encrypted and not compressed data
	 */
	public static final int DENC_DATA_STATUS_ENCRYPTED_AND_NOT_COMPRESSED = 3;
	/**
	 * encrypted and compressed data
	 */
	public static final int DENC_DATA_STATUS_ENCRYPTED_AND_COMPRESSED = 4;
	/**
	 * compression option - allways compress
	 * @deprecated should never compress
	 */
	public static final int DENC_COMPRESS_ALLWAYS = 0;
	/**
	 * compression option - never compress - default option
	 */
	public static final int DENC_COMPRESS_NEVER = 1;
	/**
	 * compression option - compress if it reduces the size of data
	 * @deprecated should never compress
	 */
	public static final int DENC_COMPRESS_BEST_EFFORT = 2;

	/**
	 *  Name atribute for storing original filename
	 */
	public static final String ENCPROP_FILENAME = "Filename";
	/**
	 *  Name atribute for storing original file size
	 */
	public static final String ENCPROP_ORIG_SIZE = "OriginalSize";
	/**
	 *  Name atribute for storing original mime type
	 */
	public static final String ENCPROP_ORIG_MIME = "OriginalMimeType";
	/**
	 *  Name atribute for storing original digidoc content
	 * info
	 */
	public static final String ENCPROP_ORIG_FILE = "orig_file";
	/**
	 *  Name atribute for storing library version that
	 * generated it
	 */
	public static final String ENCPROP_LIB_VER = "LibraryVersion";
	/**
	 *  Name atribute for storing document format and version
	 */
	public static final String ENCPROP_FORMAT_VER = "DocumentFormat";

	/**
	 * the only supported format is ENCDOC-XML
	 */
	public static final String FORMAT_ENCDOC_XML = "ENCDOC-XML";
	/**
	 * supported version is 1.0
	 */
	public static final String VERSION_1_0 = "1.0";
	
	public static final String DIGDOC_ENCRYPT_KEY_ALG = "AES";
	public static final String DIGIDOC_ENCRYPTION_ALOGORITHM2 = "AES/CBC/PKCS7Padding";
	public static final String DIGIDOC_ENCRYPTION_ALOGORITHM = "AES/CBC/NOPadding";
	public static final String DIGIDOC_SECRANDOM_ALGORITHM = "SHA1PRNG";
	public static final String DIGIDOC_KEY_ALOGORITHM = "RSA/NONE/PKCS1Padding";
	public static final boolean DIGDOC_ENCRYPT_USE_IV = true;
	public static final String DIGIDOC_SECURITY_PROVIDER_NAME = "BC";
	public static final String DIGIDOC_SECURITY_PROVIDER = "org.bouncycastle.jce.provider.BouncyCastleProvider";

	/**
	 * Constructor for EncryptedData
	 * @param id Id atribute value (optional)
	 * @param type Type atribute value (optional)
	 * @param mimeType MimeType atribute value (optional)
	 * @param xmlns xmlns atribute value. Must be
	 *            http://www.w3.org/2001/04/xmlenc#
	 * @param encryptionMethod EncryptionMethod> sublements atribute Algorithm
	 *            value (required)
	 * @throws DigiDocException for validation errors
	 */
	public EncryptedData(String id, String type, String mimeType, String xmlns,
			String encryptionMethod) throws DigiDocException {
		setId(id);
		setType(type);
		setMimeType(mimeType);
		setXmlns(xmlns);
		setEncryptionMethod(encryptionMethod);
		m_data = null;
		m_transportKey = null;
		m_nDataStatus = DENC_DATA_STATUS_UNINITIALIZED;
		m_arrEncryptedKeys = null;
		m_encProperties = null;
		// create default porperties
		setPropLibraryNameAndVersion();
		setPropFormatNameAndVersion();
	}

	/**
	 * Constructor for EncryptedData without parameters This is to be used only
	 * in SAX parser because it initializes instance variables to default
	 * values.
	 * @param xmlns xmlns atribute value. Must be
	 *            http://www.w3.org/2001/04/xmlenc#
	 * @throws DigiDocException for validation errors
	 */
	public EncryptedData(String xmlns) throws DigiDocException {
		m_id = null;
		m_type = null;
		m_mimeType = null;
		setXmlns(xmlns);
		m_encryptionMethod = null;// invalid state!
		m_data = null;
		m_transportKey = null;
		m_nDataStatus = DENC_DATA_STATUS_UNINITIALIZED;
		m_arrEncryptedKeys = null;
		m_encProperties = null;
	}

	/**
	 * Returns the data's current status
	 * @return data's current status
	 */
	public int getDataStatus() {
		return m_nDataStatus;
	}

	/**
	 * sets data status
	 * @param status new status for data
	 */
	public void setDataStatus(int status) {
		m_nDataStatus = status;
	}

	/**
	 * Retrieves data
	 * @return data
	 */
	public byte[] getData() {
		return m_data;
	}

	/**
	 * sets data
	 * @param data new data
	 */
	public void setData(byte[] data) {
		m_data = data;
	}

	/**
	 * Accessor for secret key
	 * @return SecretKey object
	 */
	public SecretKey getTransportKey() {
		return m_transportKey;
	}

	/**
	 * Mutator for secret key
	 * @param key new secret key
	 */
	public void setTransportKey(SecretKey key) {
		m_transportKey = key;
	}

	/**
	 * Accessor for id attribute
	 * @return value of Id attribute
	 */
	public String getId() {
		return m_id;
	}

	/**
	 * Mutator for Id attribute
	 * @param str new value for Id attribute
	 */
	public void setId(String str) {
		m_id = str;
	}

	/**
	 * Accessor for Type attribute
	 * @return value of Type attribute
	 */
	public String getType() {
		return m_type;
	}

	/**
	 * Mutator for Type attribute
	 * @param str new value for Type attribute
	 */
	public void setType(String str) {
		m_type = str;
	}

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

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

	/**
	 * Accessor for EncryptionMethod attribute
	 * @return value of EncryptionMethod attribute
	 */
	public String getEncryptionMethod() {
		return m_encryptionMethod;
	}

	/**
	 * Mutator for EncryptionMethod attribute
	 * @param str new value for EncryptionMethod attribute
	 * @throws DigiDocException for validation errors
	 */
	public void setEncryptionMethod(String str) throws DigiDocException {
		DigiDocException ex = validateEncryptionMethod(str);
		if (ex != null) {
			throw ex;
		}
		m_encryptionMethod = str;
	}

	/**
	 * Helper method to validate EncryptionMethod atribute
	 * @param str input data
	 * @return exception or null for ok
	 */
	private DigiDocException validateEncryptionMethod(String str) {
		DigiDocException ex = null;
		if (str == null || !str.equals(EncryptedData.DENC_ENC_METHOD_AES128)) {
			ex = new DigiDocException(
					DigiDocException.ERR_XMLENC_ENCDATA_ENCRYPTION_METHOD,
					"EncryptionMethod atribute is required and currently the only supported value is: "
							+ EncryptedData.DENC_ENC_METHOD_AES128, null);
		}
		return ex;
	}

	/**
	 * Accessor for Xmlns attribute
	 * @return value of Xmlns attribute
	 */
	public String getXmlns() {
		return m_xmlns;
	}

	/**
	 * Mutator for Xmlns attribute
	 * @param str new value for Xmlns attribute
	 * @throws DigiDocException for validation errors
	 */
	public void setXmlns(String str) throws DigiDocException {
		DigiDocException ex = validateXmlns(str);
		if (ex != null) {
			throw ex;
		}
		m_xmlns = str;
	}

	/**
	 * Helper method to validate Xmlns atribute
	 * @param str input data
	 * @return exception or null for ok
	 */
	private DigiDocException validateXmlns(String str) {
		DigiDocException ex = null;
		if (str == null || !str.equals(EncryptedData.DENC_XMLNS_XMLENC)) {
			ex = new DigiDocException(
					DigiDocException.ERR_XMLENC_ENCDATA_XMLNS,
					"xmlns atribute is required and currently the only supported value is: "
							+ EncryptedData.DENC_XMLNS_XMLENC, null);
		}
		return ex;
	}

	/**
	 * Retrieves the Id atribute value on  child element
	 * @return id value of Id atribute
	 */
	public String getEncryptionPropertiesId() {
		if (m_encProperties != null) {
			return m_encProperties.getId();
		} else {
			return null;
		}
	}

	/**
	 * Sets the Id atribute value on  child element
	 * @param id value of Id atribute
	 */
	public void setEncryptionPropertiesId(String id) {
		if (m_encProperties == null) {
			m_encProperties = new EncryptionProperties(id);
		} else {
			m_encProperties.setId(id);
		}
	}

	/**
	 * Encrypts input file to output file
	 * @param sInFile input file
	 * @param sOutFile output file
	 * @return true for success
	 */
	public boolean encryptFileData(String sInFile, String sOutFile)
	{
		try {
			int nCompressOption = EncryptedData.DENC_COMPRESS_BEST_EFFORT;
			byte[] inData = SignedDoc.readFile(new File(sInFile));
			// TODO: check cdoc existencs
			setData(inData);
			setDataStatus(EncryptedData.DENC_DATA_STATUS_UNENCRYPTED_AND_NOT_COMPRESSED);
			addProperty(EncryptedData.ENCPROP_FILENAME, sInFile);
			if(sInFile.endsWith(".bdoc") || sInFile.endsWith(".asice"))
				setMimeType(SignedDoc.MIMET_FILE_CONTENT_20);
			addProperty(EncryptedData.ENCPROP_ORIG_SIZE, new Long(inData.length).toString());
			encrypt(EncryptedData.DENC_COMPRESS_NEVER);
			FileOutputStream fos = new FileOutputStream(sOutFile);
			fos.write(toXML());
			fos.close();
			return true;
		} catch(Exception ex) {
			m_logger.error("Error encrypting from: " + sInFile + " to: " + sOutFile + " - " + ex);
			ex.printStackTrace(System.err);
		}
		return false;
	}
	
	/**
	 * Adds an  object to the array of properties
	 * @param prop new property object to be added
	 */
	public void addProperty(EncryptionProperty prop) {
		if (m_encProperties == null) {
			m_encProperties = new EncryptionProperties(null);
		}
		m_encProperties.addProperty(prop);
	}

	/**
	 * Simply adds an  object with the given Name atribute
	 * and content to the list
	 * @param name  object's Name atribute value
	 * @param content  object's content
	 */
	public void addProperty(String name, String content)
			throws DigiDocException {
		if (m_encProperties == null) {
			m_encProperties = new EncryptionProperties(null);
		}
		m_encProperties.addProperty(new EncryptionProperty(name, content));
	}

	/**
	 * Rturns the number of  objects in the list
	 * @return number of  objects
	 */
	public int getNumProperties() {
		return ((m_encProperties == null) ? 0 : m_encProperties
				.getNumProperties());
	}

	/**
	 * Returns the n-th  object
	 * @param nIdx index of the property
	 * @return the desired  object or null
	 */
	public EncryptionProperty getProperty(int nIdx) {
		if (nIdx < getNumProperties()) {
			return m_encProperties.getProperty(nIdx);
		} else {
			return null;
		}
	}

	/**
	 * Returns the  object with the given Id atribute
	 * @param id the desired objects Id atribute value
	 * @return the desired  object or null
	 */
	public EncryptionProperty findPropertyById(String id) {
		if (m_encProperties != null) {
			return m_encProperties.findPropertyById(id);
		} else {
			return null;
		}
	}

	/**
	 * Returns the  object with the given Id atribute
	 * @param name the desired objects Name atribute value
	 * @return the desired  object or null
	 */
	public EncryptionProperty findPropertyByName(String name) {
		if (m_encProperties != null) {
			return m_encProperties.findPropertyByName(name);
		} else {
			return null;
		}
	}

	/**
	 * Returns the last  object
	 * @return the desired  object or null
	 */
	public EncryptionProperty getLastProperty() {
		if (m_encProperties != null && m_encProperties.getNumProperties() > 0) {
			return m_encProperties.getProperty(m_encProperties
					.getNumProperties() - 1);
		} else {
			return null;
		}
	}

	/**
	 * Returns the content of the  object with the gine Name
	 * atribute value
	 * @param name Name atribute value
	 * @return content of the  object or null
	 */
	public String findPropertyContentByName(String name) {
		EncryptionProperty prop = findPropertyByName(name);
		if (prop != null) {
			return prop.getContent();
		} else {
			return null;
		}
	}

	/**
	 * Creates a new  or uses and existing one to store the
	 * library namd and version used to create this encrypted document
	 * @throws DigiDocException
	 */
	public void setPropLibraryNameAndVersion() throws DigiDocException {
		EncryptionProperty prop = findPropertyByName(ENCPROP_LIB_VER);
		StringBuffer sb = new StringBuffer();
		sb.append(SignedDoc.LIB_NAME);
		sb.append("|");
		sb.append(SignedDoc.LIB_VERSION);
		if (prop == null) {
			prop = new EncryptionProperty(ENCPROP_LIB_VER, sb.toString());
			addProperty(prop);
		} else {
			prop.setContent(sb.toString());
		}
	}

	/**
	 * Returns the library name that created this document. This is stored in an
	 *  element
	 * @throws DigiDocException
	 */
	public String getPropLibraryName() {
		StringBuffer sb = new StringBuffer();
		EncryptionProperty prop = findPropertyByName(ENCPROP_LIB_VER);
		if (prop != null) {
			String content = prop.getContent();
			int nIdx1 = 0;
			if ((content != null) && ((nIdx1 = content.indexOf("|")) != -1)) {
				sb.append(content.substring(0, nIdx1));
			}
		}
		return sb.toString();
	}

	/**
	 * Returns the library version that created this document. This is stored in
	 * an  element
	 * @throws DigiDocException
	 */
	public String getPropLibraryVersion() {
		StringBuffer sb = new StringBuffer();
		EncryptionProperty prop = findPropertyByName(ENCPROP_LIB_VER);
		if (prop != null) {
			String content = prop.getContent();
			int nIdx1 = 0;
			if ((content != null) && ((nIdx1 = content.indexOf("|")) != -1)) {
				sb.append(content.substring(nIdx1 + 1));
			}
		}
		return sb.toString();
	}

	/**
	 * Creates a new  or uses and existing one to store the
	 * encrypted document format name and version
	 * @throws DigiDocException
	 */
	public void setPropFormatNameAndVersion() throws DigiDocException {
		EncryptionProperty prop = findPropertyByName(ENCPROP_FORMAT_VER);
		StringBuffer sb = new StringBuffer();
		sb.append(FORMAT_ENCDOC_XML);
		sb.append("|");
		sb.append(VERSION_1_0);
		if (prop == null) {
			prop = new EncryptionProperty(ENCPROP_FORMAT_VER, sb.toString());
			addProperty(prop);
		} else {
			prop.setContent(sb.toString());
		}
	}

	/**
	 * Returns the encrypted document format name. This is stored in an
	 *  element
	 * @throws DigiDocException
	 */
	public String getPropFormatName() {
		StringBuffer sb = new StringBuffer();
		EncryptionProperty prop = findPropertyByName(ENCPROP_FORMAT_VER);
		if (prop != null) {
			String content = prop.getContent();
			int nIdx1 = 0;
			if ((content != null) && ((nIdx1 = content.indexOf("|")) != -1)) {
				sb.append(content.substring(0, nIdx1));
			}
		}
		return sb.toString();
	}

	/**
	 * Returns the encrypted document format version. This is stored in an
	 *  element
	 * @throws DigiDocException
	 */
	public String getPropFormatVersion() {
		StringBuffer sb = new StringBuffer();
		EncryptionProperty prop = findPropertyByName(ENCPROP_FORMAT_VER);
		if (prop != null) {
			String content = prop.getContent();
			int nIdx1 = 0;
			if ((content != null) && ((nIdx1 = content.indexOf("|")) != -1)) {
				sb.append(content.substring(nIdx1 + 1));
			}
		}
		return sb.toString();
	}

	/**
	 * Creates a number of  objects to store the meta info
	 * about the contents of this digidoc
	 * @throws DigiDocException
	 */
	public void setPropRegisterDigiDoc(SignedDoc sdoc) throws DigiDocException {
		for (int i = 0; i < sdoc.countDataFiles(); i++) {
			DataFile df = sdoc.getDataFile(i);
			StringBuffer sb = new StringBuffer();
			sb.append(df.getFileName());
			sb.append("|");
			sb.append(new Long(df.getSize()).toString());
			sb.append("|");
			sb.append(df.getMimeType());
			sb.append("|");
			sb.append(df.getId());
			addProperty(new EncryptionProperty(ENCPROP_ORIG_FILE, sb.toString()));
		}
	}

	/**
	 * counts the number of  objects used for stroring
	 * digidoc meta info
	 * @return count of such  objects
	 * @throws DigiDocException
	 */
	public int getPropOrigFileCount() {
		int n = 0;
		for (int i = 0; (m_encProperties != null)
				&& (i < m_encProperties.getNumProperties()); i++) {
			EncryptionProperty prop = m_encProperties.getProperty(i);
			if (prop.getName() != null
					&& prop.getName().equals(ENCPROP_ORIG_FILE)) {
				n++;
			}
		}
		return n;
	}

	/**
	 * Returns the filename part of the given embedded digidoc metadata item.
	 * @param nProp index of digidoc metadata properties
	 * @return filename part of the given property
	 * @throws DigiDocException
	 */
	public String getPropOrigFileName(int nProp) {
		String str = null;
		int n = 0, nIdx1 = 0;
		for (int i = 0; (m_encProperties != null)
				&& (i < m_encProperties.getNumProperties()); i++) {
			EncryptionProperty prop = m_encProperties.getProperty(i);
			if (prop.getName() != null
					&& prop.getName().equals(ENCPROP_ORIG_FILE)) {
				n++;
				if (n == nProp) {// the right property
					String content = prop.getContent();
					if ((content != null)
							&& ((nIdx1 = content.indexOf("|")) != -1)) {
						str = content.substring(0, nIdx1);
					}
					break;
				}
			}
		}
		return str;
	}

	
	/**
	 * Returns the desired property value
	 * @param sName name of properties
	 * @return property value
	 * @throws DigiDocException
	 */
	public String getPropByName(String sName) {
		for (int i = 0; (m_encProperties != null) && (i < m_encProperties.getNumProperties()); i++) {
			EncryptionProperty prop = m_encProperties.getProperty(i);
			if (prop.getName() != null && prop.getName().equals(sName)) 
				return prop.getContent();
		}
		return null;
	}
	
	/**
	 * Returns the filesize part of the given embedded digidoc metadata item.
	 * @param nProp index of digidoc metadata properties
	 * @return filesize part of the given property
	 * @throws DigiDocException
	 */
	public String getPropOrigFileSize(int nProp) {
		String str = null;
		int n = 0, nIdx1 = 0, nIdx2 = 0;
		for (int i = 0; (m_encProperties != null)
				&& (i < m_encProperties.getNumProperties()); i++) {
			EncryptionProperty prop = m_encProperties.getProperty(i);
			if (prop.getName() != null
					&& prop.getName().equals(ENCPROP_ORIG_FILE)) {
				n++;
				if (n == nProp) {// the right property
					String content = prop.getContent();
					if ((content != null)
							&& ((nIdx1 = content.indexOf("|")) != -1)
							&& ((nIdx2 = content.indexOf("|", nIdx1 + 1)) != -1)) {
						str = content.substring(nIdx1 + 1, nIdx2);
					}
					break;
				}
			}
		}
		return str;
	}

	/**
	 * Returns the mimetype part of the given embedded digidoc metadata item.
	 * @param nProp index of digidoc metadata properties
	 * @return mimetype part of the given property
	 * @throws DigiDocException
	 */
	public String getPropOrigFileMime(int nProp) {
		String str = null;
		int n = 0, nIdx1 = 0, nIdx2 = 0, nIdx3 = 0;
		for (int i = 0; (m_encProperties != null)
				&& (i < m_encProperties.getNumProperties()); i++) {
			EncryptionProperty prop = m_encProperties.getProperty(i);
			if (prop.getName() != null
					&& prop.getName().equals(ENCPROP_ORIG_FILE)) {
				n++;
				if (n == nProp) {// the right property
					String content = prop.getContent();
					if ((content != null)
							&& ((nIdx3 = content.indexOf("|")) != -1)
							&& ((nIdx1 = content.indexOf("|", nIdx3 + 1)) != -1)
							&& ((nIdx2 = content.indexOf("|", nIdx1 + 1)) != -1)) {
						str = content.substring(nIdx1 + 1, nIdx2);
					}
					break;
				}
			}
		}
		return str;
	}

	/**
	 * Returns the id part of the given embedded digidoc metadata item.
	 * @param nProp index of digidoc metadata properties
	 * @return id part of the given property
	 * @throws DigiDocException
	 */
	public String getPropOrigFileId(int nProp) {
		String str = null;
		int n = 0, nIdx1 = 0;
		for (int i = 0; (m_encProperties != null)
				&& (i < m_encProperties.getNumProperties()); i++) {
			EncryptionProperty prop = m_encProperties.getProperty(i);
			if (prop.getName() != null
					&& prop.getName().equals(ENCPROP_ORIG_FILE)) {
				n++;
				if (n == nProp) {// the right property
					String content = prop.getContent();
					if ((content != null)
							&& ((nIdx1 = content.lastIndexOf("|")) != -1)) {
						str = content.substring(nIdx1 + 1);
					}
					break;
				}
			}
		}
		return str;
	}

	/**
	 * Adds an  object to the array of keys
	 * @param prop new property object to be added
	 */
	public void addEncryptedKey(EncryptedKey key) {
		if (m_arrEncryptedKeys == null) {
			m_arrEncryptedKeys = new ArrayList();
		}
		m_arrEncryptedKeys.add(key);
	}

	/**
	 * Rturns the number of  objects in the list
	 * @return number of  objects
	 */
	public int getNumKeys() {
		return ((m_arrEncryptedKeys == null) ? 0 : m_arrEncryptedKeys.size());
	}

	/**
	 * Returns the n-th  object
	 * @param nIdx index of the key
	 * @return the desired  object or null
	 */
	public EncryptedKey getEncryptedKey(int nIdx) {
		if (nIdx < getNumKeys()) {
			return (EncryptedKey) m_arrEncryptedKeys.get(nIdx);
		} else {
			return null;
		}
	}

	/**
	 * Returns the last  object
	 * @return the last  object or null
	 */
	public EncryptedKey getLastEncryptedKey() {
		if (m_arrEncryptedKeys != null && m_arrEncryptedKeys.size() > 0) {
			return (EncryptedKey) m_arrEncryptedKeys.get(m_arrEncryptedKeys
					.size() - 1);
		} else {
			return null;
		}
	}

	/**
	 * Returns the  object with the given Id atribute
	 * @param id the desired objects Id atribute value
	 * @return the desired  object or null
	 */
	public EncryptedKey findKeyById(String id) {
		for (int i = 0; (m_arrEncryptedKeys != null)
				&& (i < m_arrEncryptedKeys.size()); i++) {
			EncryptedKey key = (EncryptedKey) m_arrEncryptedKeys.get(i);
			if (key.getId() != null && key.getId().equals(id)) {
				return key;
			}
		}
		return null;
	}

	/**
	 * Returns the  object with the given Recipient atribute
	 * @param recv the desired objects Recipient atribute value
	 * @return the desired  object or null
	 */
	public EncryptedKey findKeyByRecipient(String recv) {
		for (int i = 0; (m_arrEncryptedKeys != null)
				&& (i < m_arrEncryptedKeys.size()); i++) {
			EncryptedKey key = (EncryptedKey) m_arrEncryptedKeys.get(i);
			if (key.getRecipient() != null && key.getRecipient().equals(recv)) {
				return key;
			}
		}
		return null;
	}

	/**
	 * Returns the  object with the given recipients cert that has
	 * this subject DN atribute
	 * @param subjectDN the desired objects cert's subject DN
	 * @return the desired  object or null
	 */
	/*
	 * public EncryptedKey findKeyByCert(X509) throws IOException {
	 * X509CertSelector certSelector = new X509CertSelector();
	 * certSelector.setSubject(subjectDN); for(int i = 0; (m_arrEncryptedKeys !=
	 * null) && (i < m_arrEncryptedKeys.size()); i++) { EncryptedKey key =
	 * (EncryptedKey)m_arrEncryptedKeys.get(i);
	 * if(key.getRecipientsCertificate() != null &&
	 * certSelector.match(key.getRecipientsCertificate())) return key; } return
	 * null; }
	 */

	/**
	 * Generates the session key
	 * @throws DigiDocException for all initialization errors
	 */
	private void initKey() throws DigiDocException {
		// add BouncyCastle provider if not done yet
		try {
			Security.addProvider((Provider) Class.forName(
					DIGIDOC_SECURITY_PROVIDER).newInstance());
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_NO_PROVIDER);
		}
		// check key status first
		if (m_transportKey != null) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_KEY_STATUS,
					"Transport key allready initialized!", null);
		}
		try {
			SecureRandom random = SecureRandom
					.getInstance(DIGIDOC_SECRANDOM_ALGORITHM);
			KeyGenerator keygen = KeyGenerator.getInstance(
					DIGDOC_ENCRYPT_KEY_ALG, DIGIDOC_SECURITY_PROVIDER_NAME);
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Keygen:" + keygen.getClass().getName()
						+ " algorithm: " + keygen.getAlgorithm());
			}
			keygen.init(128, random);
			m_transportKey = keygen.generateKey();
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("key0: " + m_transportKey.getEncoded().length);
			}
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_KEY_GEN);
		}
		// check the result
		if (m_transportKey == null) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_KEY_GEN,
					"Failure to initialize the transport key!", null);
		}
	}

	/**
	 * Helper method to convert secret key to a Cipher
	 * @param mode ciphers mode: encrypt or decrypt
	 * @param transportKey secret key. Use null for default
	 * @param iv init vector data. Use null for no IV
	 * @return Cipher object
	 */
	public Cipher getCipher(int mode, SecretKey transportKey, byte[] iv)
			throws DigiDocException {
		Cipher cip = null;
		byte[] ivdata = null;
		// check key status first - nothing to encrypt?
		if (m_transportKey == null && transportKey == null) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_KEY_STATUS,
					"Transport key has not been initialized!", null);
		}
		try {
			if (mode == Cipher.DECRYPT_MODE) {
				cip = Cipher.getInstance(DIGIDOC_ENCRYPTION_ALOGORITHM,
						DIGIDOC_SECURITY_PROVIDER_NAME);
				IvParameterSpec ivSpec = new IvParameterSpec(iv);
				cip.init(mode, ((transportKey == null) ? m_transportKey
						: transportKey), ivSpec);
			} else {
				cip = Cipher.getInstance(DIGIDOC_ENCRYPTION_ALOGORITHM2,
						DIGIDOC_SECURITY_PROVIDER_NAME);
				cip.init(mode, ((transportKey == null) ? m_transportKey
						: transportKey));
				ivdata = cip.getIV();
				System.arraycopy(ivdata, 0, iv, 0, 16);// copy the iv used
			}
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Cipher: " + cip.getAlgorithm() + " provider: "
						+ cip.getProvider().getName());
				ivdata = cip.getIV();
				// for(int i = 0; i < ivdata.length; i++)
				// m_logger.debug("IV pos: " + i + " = " + ivdata[i]);
				// cip.getProvider().list(System.out);
			}
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_ENCRYPT);
		}
		return cip;
	}

	/**
	 * Encrypts the data with the given
	 * @param nCompressOption compression option: allways, never or best effort
	 * @throws DigiDocException for encryption errors
	 */
	public void encrypt(int nCompressOption) throws DigiDocException {
		byte[] ivdata = new byte[16];
		// check the transport key
		if (m_transportKey == null) {
			initKey();
		}
		// check data
		if (m_data == null
				|| (m_nDataStatus != DENC_DATA_STATUS_UNENCRYPTED_AND_COMPRESSED && m_nDataStatus != DENC_DATA_STATUS_UNENCRYPTED_AND_NOT_COMPRESSED)) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_DATA_STATUS,
					"Invalid data status for encryption operation!", null);
		}
		int nTotalInput = m_data.length, nTotalCompressed = 0, nTotalEncrypted = 0;
		// compress data if necessary
		compress(nCompressOption);
		nTotalCompressed = m_data.length;
		// get cipher to encrypt the data
		Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, null, ivdata);
		try {
			// get the cipher
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Encrypt - algorithm: " + cipher.getAlgorithm()
						+ " blocksize: " + cipher.getBlockSize());
			}
			int nBlockSize = cipher.getBlockSize();
			byte[] cdata = null;
			int nOrigLen = m_data.length;
			int nInput = nOrigLen;
			// encrypt full data blocks
			int nLastBlockSize = m_data.length % nBlockSize;
			nInput = nOrigLen - nLastBlockSize + nBlockSize;
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			bos.write(m_data, 0, m_data.length - nLastBlockSize);
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Encrypt - body: " + m_data.length
						+ " full-data: " + (m_data.length - nLastBlockSize)
						+ " left: " + nLastBlockSize);
			}
			// compose the last block
			cdata = new byte[nBlockSize];
			if(nLastBlockSize > 0) {
			  System.arraycopy(m_data, m_data.length - nLastBlockSize, cdata, 0, nLastBlockSize);
			  for (int i = nLastBlockSize; i < nBlockSize; i++) 
				cdata[i] = 0;// pad with zeros
			  // last byte contains the amount of pad-bytes in this block
			  cdata[nBlockSize - 1] = new Integer(nBlockSize - nLastBlockSize).byteValue();
			} else { // full x.923 padding block
				for (int i = nLastBlockSize; i < nBlockSize; i++) 
					cdata[i] = 0;
				cdata[nBlockSize - 1] = 16;
			}
			bos.write(cdata);
			if (m_logger.isDebugEnabled()) {
				for (int i = 0; i < nBlockSize; i++) {
					m_logger.debug("Byte at: " + i + " = " + cdata[i]);
				}
			}
			// encrypt data
			cdata = cipher.doFinal(bos.toByteArray());
			
			nTotalEncrypted = cdata.length;
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Encrypt - orig: " + nOrigLen + " input: "
						+ nInput + " encrypted: " + cdata.length);
			}
			// encrypted data
			m_data = new byte[cdata.length + 16];
			System.arraycopy(ivdata, 0, m_data, 0, 16);
			System.arraycopy(cdata, 0, m_data, 16, cdata.length);
			m_nDataStatus = DENC_DATA_STATUS_ENCRYPTED_AND_NOT_COMPRESSED;
			// encrypt transport key for all recipients
			for (int i = 0; i < getNumKeys(); i++) {
				EncryptedKey ekey = getEncryptedKey(i);
				ekey.encryptKey(this);
			}
			if (m_logger.isInfoEnabled()) {
				m_logger.info("Encrypt total - input: " + nTotalInput
						+ " compressed: " + nTotalCompressed + " encrypted: "
						+ nTotalEncrypted);
			}
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_ENCRYPT);
		}
	}

	/**
	 * Returns EncryptedKey index by desired cert if found
	 * @param cert recipients cert to search
	 * @return EncryptedKey index by desired cert if found or -1 if not found
	 */
	public int getRecvIndex(X509Certificate cert) {
		if(cert == null) return -1;
		for (int i = 0; (m_arrEncryptedKeys != null)
				&& (i < m_arrEncryptedKeys.size()); i++) {
			EncryptedKey key = (EncryptedKey) m_arrEncryptedKeys.get(i);
			if (key.getRecipientsCertificate() != null
					&& key.getRecipientsCertificate().getSerialNumber().equals(
							cert.getSerialNumber()))
				return i;
		}
		return -1;
	}

	/**
	 * Decrypts the data with the given token and pin
	 * @param key index of key in detected keys array (by pkcs11 factory)
	 * @param token index of detected token
	 * @param pin authentication/decryption pin
	 * @throws DigiDocException for decryption errors
	 */
	public void decrypt(int nKey, int token, String pin)
			throws DigiDocException 
	{
		EncryptedKey ekey = getEncryptedKey(nKey);
		try {
			// decrypt transport key
			SignatureFactory sfac = ConfigManager.instance().getSignatureFactory();
			if (m_logger.isDebugEnabled())
				m_logger.debug("Decrypting key: " + nKey + " with token: " + token);
			byte[] decdata = sfac.decrypt(ekey.getTransportKeyData(), token, pin);
			decryptWithKey(decdata);
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_DECRYPT);
		}
	}

	/**
	 * Decrypts the data with the given keystore
	 * @param key index of key in detected keys array
	 * @param keystore keystore filename
	 * @param storepass keystore password
	 * @param storetype keystore type
	 * @throws DigiDocException for decryption errors
	 */
	public void decryptPkcs12(int nKey, String keystore, String storepass, String storetype) 
		throws DigiDocException 
	{
		EncryptedKey ekey = getEncryptedKey(nKey);
		try {
			// decrypt transport key
			Pkcs12SignatureFactory p12fac = new Pkcs12SignatureFactory();
			p12fac.init();
			p12fac.load(keystore, storetype, storepass);
			if (m_logger.isDebugEnabled())
				m_logger.debug("Decrypting key: " + nKey + " with token: " + nKey);
			byte[] decdata = p12fac.decrypt(ekey.getTransportKeyData(), 0, storepass);
			decryptWithKey(decdata);
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_DECRYPT);
		}
	}
	
	/**
	 * Decrypts the data with the given token and pin
	 * @param driver type PKCS11/PKCS12/PKCS11_SUN
	 * @param keystoreFile keystore file for PKCS12 driver
	 * @param key index of key in detected keys array (by pkcs11 factory)
	 * @param token index of detected token
	 * @param pin authentication/decryption pin
	 * @throws DigiDocException for decryption errors
	 */
	public void decrypt(String driver, String keystoreFile, int nKey, int token, String pin)
			throws DigiDocException 
	{
		EncryptedKey ekey = getEncryptedKey(nKey);
		boolean bOk = true;
		try {
			// decrypt transport key
			ConfigManager cfg = ConfigManager.instance();
			SignatureFactory sigFac = cfg.getSignatureFactoryOfType(driver);
			if(sigFac == null) {
				m_logger.error("No signature factory of type: " + driver);
				throw new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "No signature factory of type: " + driver, null);
			} else {
				if(sigFac.getType().equals(SignatureFactory.SIGFAC_TYPE_PKCS12)) {
				  Pkcs12SignatureFactory p12sfac = (Pkcs12SignatureFactory)sigFac;
				  bOk = p12sfac.load(keystoreFile, SignatureFactory.SIGFAC_TYPE_PKCS12, pin);
				}
			}
			if(!bOk) {
				m_logger.error("Failed to load signature token!");
				throw new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "Failed to load signature token!", null);
			}
			if(ekey == null) {
				m_logger.error("No recipient nr: " + nKey);
				throw new DigiDocException(DigiDocException.ERR_XMLENC_DECRYPT, "No recipient nr: " + nKey, null);
			}
			if(m_logger.isDebugEnabled())
				m_logger.debug("Decrypting key: " + nKey + " with token: " + token + 
						" key-data: " + ((ekey.getTransportKeyData() != null) ? ekey.getTransportKeyData().length : 0));
			byte[] decdata = sigFac.decrypt(ekey.getTransportKeyData(), token, pin);
			decryptWithKey(decdata);
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_DECRYPT);
		}
	}
	
	/**
	 * Decrypts the data with the given keystore
	 * @param key index of key in detected keys array
	 * @param keystore keystore filename
	 * @param storepass keystore password
	 * @param storetype keystore type
	 * @param inStream input stream
	 * @param outStream output stream
	 * @param recipientName recipient name
	 * @throws DigiDocException for decryption errors
	 */
	/*public static void decryptPkcs12Stream(int nKey, String keystore, String storepass,
			String storetype, InputStream inStream, OutputStream outStream, String recipientName) 
		throws DigiDocException 
	{
		try {
			// decrypt transport key
			Pkcs12SignatureFactory p12fac = new Pkcs12SignatureFactory();
			p12fac.init();
			p12fac.load(keystore, storetype, storepass);
			if (m_logger.isDebugEnabled())
				m_logger.debug("Decrypting key: " + nKey + " with token: " + nKey);
			byte[] deckey = p12fac.decrypt(ekey.getTransportKeyData(), nKey, storepass);
			EncryptedStreamSAXParser parser = new EncryptedStreamSAXParser();
			int nDec = parser.decryptStreamUsingRecipientNameAndKey(inStream,
                    outStream, deckey, recipientName);
			if (m_logger.isDebugEnabled())
				m_logger.debug("Total decrypted: " + nDec);
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_DECRYPT);
		}
	}*/

	/**
	 * Decrypts the data with the given decrypted transport key
	 * @param deckey decrypted transport key as byte array
	 * @throws DigiDocException for decryption errors
	 */
	public void decryptWithKey(byte[] deckey) throws DigiDocException {
		byte[] ivdata = {0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
		// check data
		if (m_data == null
				|| (m_nDataStatus != DENC_DATA_STATUS_ENCRYPTED_AND_COMPRESSED && m_nDataStatus != DENC_DATA_STATUS_ENCRYPTED_AND_NOT_COMPRESSED)) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_DATA_STATUS,
					"Invalid data status for decryption operation!", null);
		}
		if (m_logger.isDebugEnabled())
			m_logger.debug("Decrypting " + m_data.length + " using iv "
					+ ivdata.length + " left: " + (m_data.length - ivdata.length) );
		// use the first 16 bytes as IV value and remove it from decryption
		// process
		System.arraycopy(m_data, 0, ivdata, 0, ivdata.length);
		//for (int i = m_data.length - 16; i < 16; i++) 
		//	m_logger.debug("iv at: " + i + " = " + m_data[i]);
		try {
			m_transportKey = (SecretKey) new SecretKeySpec(deckey,
					DIGIDOC_ENCRYPTION_ALOGORITHM);
			// decrypt data
			Cipher cipher = getCipher(Cipher.DECRYPT_MODE, m_transportKey, ivdata);
			if (m_logger.isDebugEnabled())
				m_logger.debug("Decrypting: " + m_data.length + " bytes");
			m_data = cipher.doFinal(m_data, 16, m_data.length - 16);
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Decrypted data: " + m_data.length + " bytes");
				for (int i = m_data.length - 16; i < m_data.length; i++) 
					m_logger.debug("byte at: " + i + " = " + m_data[i]);
			}
			int nPadLen = new Integer(m_data[m_data.length - 1]).intValue();
			if (m_logger.isDebugEnabled())
				m_logger.debug("Decrypted: " + m_data.length
						+ " bytes, check padding: " + nPadLen);
			// try first padding
			boolean bPadOk = checkPadding(m_data, nPadLen);
			if (bPadOk)
				m_data = removePadding(m_data, nPadLen);
			// try second padding
			nPadLen = new Integer(m_data[m_data.length - 1]).intValue();
			bPadOk = checkPadding(m_data, nPadLen);
			if (bPadOk)
				m_data = removePadding(m_data, nPadLen);
			if (m_nDataStatus == DENC_DATA_STATUS_ENCRYPTED_AND_COMPRESSED)
				m_nDataStatus = DENC_DATA_STATUS_UNENCRYPTED_AND_COMPRESSED;
			if (m_nDataStatus == DENC_DATA_STATUS_ENCRYPTED_AND_NOT_COMPRESSED)
				m_nDataStatus = DENC_DATA_STATUS_UNENCRYPTED_AND_NOT_COMPRESSED;
			if (m_nDataStatus == DENC_DATA_STATUS_UNENCRYPTED_AND_COMPRESSED)
				decompress();
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_DECRYPT);
		}
	}

	private boolean checkPadding(byte[] data, int nPadLen) {
		boolean bPadOk = true;
		if (m_logger.isDebugEnabled())
			m_logger.debug("Checking padding: " + nPadLen + " bytes");
		if (nPadLen < 0 || nPadLen > 16 || data == null || data.length < nPadLen)
			return false;
		for (int i = data.length - nPadLen; nPadLen > 0 && i < data.length - 1; i++) {
			if (data[i] != 0 && nPadLen != 16) {
				if (m_logger.isDebugEnabled())
					m_logger.debug("Data at: " + i + " = " + data[i]
							+ " cancel padding");
				bPadOk = false;
				break;
			}
		}
		return bPadOk;
	}

	private byte[] removePadding(byte[] data, int nPadLen) {
		if (m_logger.isDebugEnabled())
			m_logger.debug("Removing padding: " + nPadLen + " bytes");
		byte[] data2 = new byte[data.length - nPadLen];
		System.arraycopy(data, 0, data2, 0, data.length - nPadLen);
		return data2;
	}

	/**
	 * Compresses the unencrypted data using ZLIB algorithm
	 * @param nCompressOption compression option: allways, never or best effort
	 * @throws DigiDocException for compression errors
	 */
	private void compress(int nCompressOption) throws DigiDocException {
		// check the flag
		if (nCompressOption == DENC_COMPRESS_NEVER) {
			return;// nothing to do
		}
		// check data
		if (m_data == null
				|| m_nDataStatus != DENC_DATA_STATUS_UNENCRYPTED_AND_NOT_COMPRESSED) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_DATA_STATUS,
					"Invalid data status for compression operation!", null);
		}
		try {
			int nOldSize = m_data.length;
			if (m_logger.isDebugEnabled()) {
				m_logger.debug("Compressing: " + m_data.length + " bytes");
			}
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			DeflaterOutputStream gout = new DeflaterOutputStream(bout);
			gout.write(m_data);
			gout.flush();
			gout.close();
			bout.close();
			byte[] n_data = bout.toByteArray();
			int nNewSize = n_data.length;
			if (nCompressOption == DENC_COMPRESS_ALLWAYS
					|| (nCompressOption == DENC_COMPRESS_BEST_EFFORT && nNewSize < nOldSize)) {
				m_nDataStatus = DENC_DATA_STATUS_UNENCRYPTED_AND_COMPRESSED;
				m_data = n_data;
				// store original size and mime type
				addProperty(ENCPROP_ORIG_SIZE, new Integer(nOldSize).toString());
				if (m_mimeType != null) {
					addProperty(ENCPROP_ORIG_MIME, m_mimeType);
				}
				// mark this as compressed data
				m_mimeType = DENC_ENCDATA_MIME_ZLIB;
				if (m_logger.isDebugEnabled()) {
					m_logger.debug("Keeping compressed: " + m_data.length
							+ " bytes");
				}
			} else {
				if (m_logger.isDebugEnabled()) {
					m_logger.debug("Discarding compressed: " + m_data.length
							+ " bytes");
				}
			}
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_COMPRESS);
		}
	}

	/**
	 * Decompresses the unencrypted data using ZLIB algorithm
	 * @throws DigiDocException for decompression errors
	 */
	private void decompress() throws DigiDocException {
		// check data
		if (m_data == null
				|| m_nDataStatus != DENC_DATA_STATUS_UNENCRYPTED_AND_COMPRESSED) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_DATA_STATUS,
					"Invalid data status for decompression operation!", null);
		}
		try {
			if(m_logger.isDebugEnabled()) 
				m_logger.debug("Decompressing: " + m_data.length + " bytes");
			ByteArrayInputStream bin = new ByteArrayInputStream(m_data);
			InflaterInputStream gin = new InflaterInputStream(bin);
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			byte[] data = new byte[2048];
			int nRead = 0;
			while ((nRead = gin.read(data)) > 0) 
				bos.write(data, 0, nRead);
			gin.close();
			bin.close();
			bos.close();
			m_data = bos.toByteArray();
			m_nDataStatus = DENC_DATA_STATUS_UNENCRYPTED_AND_NOT_COMPRESSED;
			if(m_logger.isDebugEnabled()) 
				m_logger.debug("Decompressed: " + m_data.length + " bytes");
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_DECOMPRESS);
		}
	}

	/**
	 * Encrypts all input data coming from this stream, compresses it if
	 * necessary converts to base64 and writes as an XML encrypted document to
	 * output stream. Use this method to encrypt big amunts of data. Please note
	 * that you must first inittialize the recipients (e.g. EncryptedKey
	 * objects) but not the data. Data comes from the input stream, thus data
	 * status should be UNINITIALIZED when you call this method.
	 * @param in stream with data to encrypt
	 * @param out output stream for encrypted data
	 * @param nCompressOption compression option: allways, never. Best-effort is
	 *            not supported here because we don't know it before we have
	 *            encrypted eveything end then we don't want to redo this.
	 * @throws DigiDocException for encryption errors
	 */
	public void encryptStream(InputStream in, OutputStream out,
			int nCompressOption) throws DigiDocException {
		byte[] ivdata = new byte[16];
		// check the transport key
		if (m_transportKey == null) {
			initKey();
		}
		// check data
		if (m_data != null || m_nDataStatus != DENC_DATA_STATUS_UNINITIALIZED) {
			throw new DigiDocException(DigiDocException.ERR_XMLENC_DATA_STATUS,
					"Invalid data status for encryption operation!", null);
		}
		// get cipher to encrypt the data
		Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, null, ivdata);
		Deflater compressor = null;
		if (nCompressOption == DENC_COMPRESS_ALLWAYS) {
			compressor = new Deflater();
			if (m_mimeType != null) {
				addProperty(ENCPROP_ORIG_MIME, m_mimeType);
			}
			// mark this as compressed data
			m_mimeType = DENC_ENCDATA_MIME_ZLIB;
		}
		int nCiphBlockSize = cipher.getBlockSize();
		int nBlockSize = 2048;
		int nTotalInput = 0, nTotalCompressed = 0, nTotalEncrypted = 0, nTotalBase64 = 0, nTotalEncInp = 0;
		int nLen1, nLen2, nB64left = 0;
		byte[] data1 = new byte[nBlockSize];
		byte[] data2 = new byte[nBlockSize * 10];
		byte[] b64leftover = new byte[65];
		try {
			// get the cipher
			if (m_logger.isDebugEnabled()) 
				m_logger.debug("EncryptStream - algorithm: " + cipher.getAlgorithm() + " blocksize: " + nCiphBlockSize);
			
			// encrypt transport key for all recipients
			for (int i = 0; i < getNumKeys(); i++) {
				EncryptedKey ekey = getEncryptedKey(i);
				ekey.encryptKey(this);
			}
			// write xml header
			out.write(xmlHeader());
			boolean isLastBlock = false;
			boolean isReadFromFile = true;
			// read input data
			do {
				if (isReadFromFile) {
					nLen1 = in.read(data1);
					if(nLen1 < 0) nLen1 = 0;
					if (m_logger.isDebugEnabled()) 
						m_logger.debug("EncryptStream - input: " + nLen1);
					nTotalInput += nLen1;
					isLastBlock = nLen1 < nBlockSize;
					if (nCompressOption == DENC_COMPRESS_ALLWAYS) { // compress
						if (nLen1 > 0) {
							compressor.setInput(data1, 0, nLen1);
						}
						if (isLastBlock) { // last block
							compressor.finish();
							nLen2 = compressor.deflate(data2);
							isReadFromFile = compressor.finished();
						} else {
							nLen2 = compressor.deflate(data2);
							if (nLen2 > 0) {
								isReadFromFile = false;
							}
						}
						nTotalCompressed += nLen2;
						if (m_logger.isDebugEnabled()) {
							m_logger.debug("EncryptStream - input: " + nLen1
									+ " compressed: " + nLen2 + " needin: "
									+ compressor.needsInput());
						}
					} else { // don't compress
						if(nLen1 > 0 && data2.length < nLen1)
							data2 = new byte[nLen1];
						if(nLen1 > 0) {
							System.arraycopy(data1, 0, data2, 0, nLen1);
							nLen2 = nLen1;
						} else
							nLen2 = 0;
					}
				} else {
					nLen1 = 0;
					nLen2 = compressor.deflate(data2);
					if (isLastBlock) {
						isReadFromFile = compressor.finished();
					} else {
						isReadFromFile = (nLen2 == 0);
					}
					nTotalCompressed += nLen2;
				}
				// encrypt data
				byte[] encdata = null;
				if (m_logger.isDebugEnabled()) 
					m_logger.debug("EncryptStream - input: " + nLen1 + " enc-input: " + nLen2);
				if (isReadFromFile && isLastBlock) { // last block
					ByteArrayOutputStream bos = new ByteArrayOutputStream();
					// last block or real data
					if (m_logger.isDebugEnabled()) {
						m_logger.debug("EncryptStream - last-data: " + nLen2);
					}
					encdata = cipher.update(data2, 0, nLen2);
					if (encdata != null) {
						bos.write(encdata);
					}
					nTotalEncInp += nLen2;
					// calculate padding
					int nLastBlockSize = nCiphBlockSize - (nTotalEncInp % nCiphBlockSize);
					if (m_logger.isDebugEnabled()) {
						m_logger.debug("EncryptStream - total-data: "
								+ nTotalEncInp + " padding: " + nLastBlockSize);
					}
					if(nLastBlockSize == 0)
						nLastBlockSize = 16; // use full x.923 padding block if necessary
					byte[] ldata = new byte[nLastBlockSize];
					for (int i = 0; i < nLastBlockSize; i++) {
						ldata[i] = 0;// pad with zeros
					}
					// last byte contains the amount of pad-bytes in this block
					ldata[nLastBlockSize - 1] = new Integer(nLastBlockSize).byteValue();
					encdata = cipher.doFinal(ldata);
					bos.write(encdata);
					encdata = bos.toByteArray();
					if (m_logger.isDebugEnabled()) {
						m_logger.debug("EncryptStream - encrypted: "
								+ ((encdata != null) ? encdata.length : 0));
					}

				} else {// not the last block
					if (nLen2 > 0) {
						encdata = cipher.update(data2, 0, nLen2);
						nTotalEncInp += nLen2;
						if (m_logger.isDebugEnabled()) {
							m_logger.debug("EncryptStream - norm input: "
									+ nLen2 + " encrypted: "
									+ ((encdata != null) ? encdata.length : 0));
						}
					}
				}
				if (encdata != null) {
					// if this is the first block then add the IV vector
					// to the beginning of data that is subsequently encoded
					if (nTotalEncrypted == 0) {
						byte[] tdata = new byte[encdata.length + 16];
						System.arraycopy(ivdata, 0, tdata, 0, 16);
						System.arraycopy(encdata, 0, tdata, 16, encdata.length);
						nTotalEncrypted += encdata.length;
						encdata = tdata;
					} else {
						nTotalEncrypted += encdata.length;
					}
					// use also data left over from last block
					if (nB64left > 0) {
						if (m_logger.isDebugEnabled()) {
							m_logger.debug("EncryptStream - input: "
									+ encdata.length + " left: " + nB64left);
						}
						byte[] data3 = new byte[encdata.length + nB64left];
						System.arraycopy(b64leftover, 0, data3, 0, nB64left);
						System.arraycopy(encdata, 0, data3, nB64left,
								encdata.length);
						encdata = data3;
					}
					int nUsed = Base64Util.encodeToStream(encdata, out,
							(isLastBlock && isReadFromFile));
					nB64left = encdata.length - nUsed;
					if (m_logger.isDebugEnabled()) {
						m_logger.debug("EncryptStream - input: "
								+ encdata.length + " used: " + nUsed
								+ " copy: " + nB64left + " pos: " + nUsed);
					}
					if (nB64left > 0) {
						System.arraycopy(encdata, nUsed, b64leftover, 0,
								nB64left);
						nTotalBase64 += (nUsed / 3) * 4;
						if (m_logger.isDebugEnabled()) {
							m_logger.debug("EncryptStream - input: "
									+ encdata.length + " used: " + nUsed
									+ " base64: " + ((nUsed / 3) * 4)
									+ " left: " + nB64left);
						}
					}
				}
			} while (!isLastBlock || (nLen1 == nBlockSize || !isReadFromFile));
			addProperty(ENCPROP_ORIG_SIZE, new Integer(nTotalInput).toString());

			// write xml trailer
			out.write(xmlTrailer());
			out.flush();
			if (m_logger.isInfoEnabled()) {
				m_logger.info("EncryptStream total - input: " + nTotalInput
						+ " compressed: " + nTotalCompressed + " encrypted: "
						+ nTotalEncrypted + " base64: " + nTotalBase64);
			}
		} catch (Exception ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XMLENC_ENCRYPT);
		}
	}

	/**
	 * Converts the EncryptedData to XML form
	 * @return XML representation of EncryptedData
	 */
	public byte[] toXML() throws DigiDocException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			bos.write(xmlHeader());
			byte[] b64data = ConvertUtils.str2data(Base64Util
					.encode(m_data, 64));
			int nTotalBase64 = b64data.length;
			bos.write(b64data);
			if (m_logger.isInfoEnabled()) {
				m_logger.info("Encrypt total - base64: " + nTotalBase64);
			}
			bos.write(xmlTrailer());
		} catch (IOException ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XML_CONVERT);
		}
		return bos.toByteArray();
	}

	/**
	 * Converts the EncryptedData header (until payload data) to XML form
	 * @return XML representation of EncryptedData header
	 */
	private byte[] xmlHeader() throws DigiDocException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			bos.write(ConvertUtils
					.str2data(""));
			bos.write(ConvertUtils.str2data(""));
			bos.write(ConvertUtils
					.str2data(""));
			bos.write(ConvertUtils.str2data(""));
			for (int i = 0; i < getNumKeys(); i++) {
				EncryptedKey key = getEncryptedKey(i);
				bos.write(key.toXML());
			}
			bos.write(ConvertUtils.str2data(""));
			bos.write(ConvertUtils
					.str2data(""));
			// after this comes payload data
		} catch (IOException ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XML_CONVERT);
		}
		return bos.toByteArray();
	}

	/**
	 * Converts the EncryptedData header (until payload data) to XML form
	 * @return XML representation of EncryptedData header
	 */
	private byte[] xmlTrailer() throws DigiDocException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			// header and encrypted data comes before this
			bos.write(ConvertUtils
					.str2data(""));
			if (getNumProperties() > 0) {
				bos.write(ConvertUtils.str2data(""));
				for (int i = 0; i < getNumProperties(); i++) {
					EncryptionProperty prop = getProperty(i);
					bos.write(prop.toXML());
				}
				bos
						.write(ConvertUtils
								.str2data(""));
			}
			bos.write(ConvertUtils.str2data(""));
		} catch (IOException ex) {
			DigiDocException.handleException(ex,
					DigiDocException.ERR_XML_CONVERT);
		}
		return bos.toByteArray();
	}

	/**
	 * Helper method to validate the whole EncryptedData object
	 * @return a possibly empty list of DigiDocException objects
	 */
	public ArrayList validate() {
		ArrayList errs = new ArrayList();
		DigiDocException ex = validateEncryptionMethod(m_encryptionMethod);
		if (ex != null) {
			errs.add(ex);
		}
		ex = validateXmlns(m_xmlns);
		if (ex != null) {
			errs.add(ex);
		}
		if (m_encProperties != null) {
			ArrayList e = m_encProperties.validate();
			if (!e.isEmpty()) {
				errs.addAll(e);
			}
		}
		for (int i = 0; i < getNumKeys(); i++) {
			EncryptedKey ekey = getEncryptedKey(i);
			ArrayList e = ekey.validate();
			if (!e.isEmpty()) {
				errs.addAll(e);
			}
		}
		return errs;
	}

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy