/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.labo.utils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import java.util.regex.Pattern;

import javax.mail.Session;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.persistence.Transient;

import lu.tudor.santec.gecamed.core.utils.Logger;
import lu.tudor.santec.gecamed.labo.ejb.entity.beans.Certifier;
import lu.tudor.santec.gecamed.labo.ejb.entity.beans.LaboratoryCertificate;
import lu.tudor.santec.gecamed.labo.ejb.entity.beans.PhysicianKey;
import lu.tudor.santec.gecamed.labo.ejb.session.interfaces.ImportInterface;
import lu.tudor.santec.gecamed.labo.ejb.session.interfaces.LaboratoryInterface;

import org.apache.log4j.Level;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMEEnveloped;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;

public class SmimeDecoder implements PasswordFinder
	{
	private KeyStore        		m_Keystore			 = null;
    private String					m_PrivateKeyPassword = null; 
    private PasswordEncrypter		m_PasswordEncrypter  = null;
    private int						m_Errors			 = LaboException.ERROR_NONE;
    
	private static Logger			m_Logger  = new Logger (SmimeDecoder.class);	

//---------------------------------------------------------------------------
//***************************************************************************
//* Constants	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

	private static final int		c_BufferSize		= 4096;
	
	protected static Pattern 
    
    c_CharsetPattern = Pattern.compile ("text\\/plain; charset=(.*)",Pattern.CASE_INSENSITIVE);

    // separates the certificates in a chain (look behind -> no char will be lost)
    private static final String	CERT_SEPARATOR	= "(?<=-----END CERTIFICATE-----)";

//---------------------------------------------------------------------------
//***************************************************************************
//* Constructor	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public SmimeDecoder ()
	{
	if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
		{
		Security.addProvider(new BouncyCastleProvider());		
		m_Logger.log(Level.INFO, "Added Security Provider \"" + BouncyCastleProvider.PROVIDER_NAME +"\"!");
		}
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Primitives	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * parses the specified data in PEM (Privacy Enhanced Mail) format and returns
 * an object representing the PEM data.
 * @param p_PEMData specfies the raw data in PEM format.
 * @return an object representing the PEM data if successful or <code>null</code>
 * if an error occured.
 * @throws LaboException 
 */
//---------------------------------------------------------------------------

	public Object parsePEMData(byte[] p_PEMData) throws LaboException
	{
		BufferedReader	l_RawReader;
		PEMReader		l_PEMReader	= null;
		Object			l_PEMData	= null;
		
		if (p_PEMData == null)
			throw new LaboException(LaboException.ERROR_KEY_NOT_SET);
		
		try
		{
			l_RawReader	= new BufferedReader(new InputStreamReader(new ByteArrayInputStream(p_PEMData)));
			l_PEMReader	= new PEMReader(l_RawReader, this);
			l_PEMData	= l_PEMReader.readObject();
		}
		catch (Exception p_Exception)
		{
			m_Logger.log(Level.ERROR, "Failed to parse specified PEM data!", p_Exception);
			throw new LaboException(LaboException.ERROR_PEMDATA_NOT_PARSED);
		}
		finally
		{
			try
			{
				if (l_PEMReader != null)
					l_PEMReader.close();
			}
			catch (IOException e)
			{
				m_Logger.log(Level.ERROR, "Couldn't close BufferedReader");
			}
		}
		
		return l_PEMData;
	}

//---------------------------------------------------------------------------

private int copyData (InputStream p_Input, OutputStream p_Output)
	{
	byte []          		l_Buffer;
	int						l_BytesRead = 0;

	try	{
		try	{
			l_Buffer = new byte [c_BufferSize];
			
			do	{
				l_BytesRead = p_Input.read(l_Buffer, 0, c_BufferSize);
				if (l_BytesRead > 0) p_Output.write(l_Buffer, 0, l_BytesRead);
				}
			while (l_BytesRead > 0);
			}    
	    catch (IOException p_Exception)
	        {
	        m_Logger.log (Level.ERROR, "Error while copying Data!",p_Exception);
	        }
	    finally
	        {
	        if (p_Input != null) p_Input.close ();
	        if (p_Output != null)  p_Output.close ();
	        }    
		}
	catch (IOException p_Exception)
        {
        m_Logger.log (Level.WARN, "Failed to close streams! Returned data may be incomplete!" ,p_Exception);
        }

	return l_BytesRead;	
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
	
//---------------------------------------------------------------------------
/**
 * Reads raw PEM (Privacy Enhanced Mail) data from specified file
 * @param p_File specifies the file to read from.
 * @return a byte array holding the read data or <code>null</code> if an
 * error occured. 
 */
//---------------------------------------------------------------------------

public static byte[] getPEMFromFile (File p_File)
	{
	FileInputStream			l_Reader = null;
    ByteArrayOutputStream	l_Writer = null;
	byte []          		l_Buffer;
	int						l_BytesRead;

	if ((p_File == null) || (!p_File.canRead())) 
		{
		m_Logger.log (Level.ERROR, "Specified file either does not exist or can't be read!");
		return null;
		}
	
	try	{
		try	{
			l_Reader = new FileInputStream  (p_File);
			l_Writer = new ByteArrayOutputStream ();
			
			l_Buffer = new byte [c_BufferSize];
			
			do	{
				l_BytesRead = l_Reader.read(l_Buffer, 0, c_BufferSize);
				if (l_BytesRead > 0) l_Writer.write(l_Buffer, 0, l_BytesRead);
				}
			while (l_BytesRead > 0);
			}    
		catch (FileNotFoundException p_Exception)
	        {
	        // Shouldn't occure because existence of files has already been checked
	        // beforehand.
	        }
	    catch (IOException p_Exception)
	        {
	        m_Logger.log (Level.ERROR, "Error while reading file " + p_File.toString(),p_Exception);
	        }
	    finally
	        {
	        if (l_Reader != null) l_Reader.close ();
	        if (l_Writer != null)  l_Writer.close ();
	        }    
		}
	catch (IOException p_Exception)
        {
        m_Logger.log (Level.WARN, "Failed to close streams! Returned data may be incomplete!" ,p_Exception);
        }

	return l_Writer.toByteArray();
	}

//---------------------------------------------------------------------------

public void setPasswordEncrpyter (PasswordEncrypter p_Encrypter)
	{
	m_PasswordEncrypter = p_Encrypter;	
	}

//---------------------------------------------------------------------------

//public void setEncryptionPassword (String p_Password)
//	{
//	if (m_PasswordEncrypter != null) m_PasswordEncrypter.setEncryptionPassword(p_Password);
//	}

//---------------------------------------------------------------------------
/**
 * @throws LaboException 
 * 
 */
//---------------------------------------------------------------------------

@Transient
public X509Certificate getCertificate (byte[] p_PEMData, LaboratoryInterface p_LaboManager) throws LaboException
	{
    Object                  l_PEMData     = null;
    X509Certificate         l_Certificate = null;
    List<X509Certificate>	l_CaChain;

    if (p_PEMData == null) return null;
    
    l_PEMData = this.parsePEMData(p_PEMData);
    
    if (l_PEMData instanceof X509Certificate)
        {
        l_Certificate = (X509Certificate) l_PEMData; 
        
        l_CaChain = readChain(p_LaboManager.getCAChain());
        
        if (!Certifier.verify(l_Certificate, l_CaChain))
        	{
        	m_Errors	= LaboException.ERROR_ROOT_CA_INVALID;
        	m_Logger.log (Level.ERROR,"Specified certificate could not be verified against CA certificate chain!");
        	}
         }
      
    return l_Certificate;    
 	}

//---------------------------------------------------------------------------
/**
 * 
 */
//---------------------------------------------------------------------------

@Transient
public PrivateKey getPrivateKey (byte[] p_PEMData) throws LaboException
	{
    Object		l_PEMData		= null;   
    KeyPair		l_KeyPair		= null;
    PrivateKey	l_PrivateKey	= null;
    
    if (p_PEMData == null) return null;

    l_PEMData = this.parsePEMData(p_PEMData);
	
    if (l_PEMData instanceof KeyPair)
        {
        l_KeyPair = (KeyPair) l_PEMData; 
        l_PrivateKey = l_KeyPair.getPrivate();
        }
      
    return l_PrivateKey;    
 	}

//---------------------------------------------------------------------------
/** Reads the certificate specified by p_CertificateFilePath and the private key specified by
 * p_PrivateKeyFilePath and stores the keypair in a newly created keystore.
 * @param p_CertificateFilePath specifies the name of the PEM file containing the Certificate
 * @param p_PrivateKeyFilePath specifies the name of the PEM file containing the private key
 * @param p_PrivateKeyPassword specifies the password required to access the private key. If null is specified,
 * the password previously set by setPrivateKeyPassword will be used.
 * @param p_KeystorePassword specifies the password to be set for the newly created keystore. If null is specified,
 * the password previously set by setKeystorePassword will be used.
 * @param p_KeypairAlias specifies the alias to be used to identify the private key / public key pair in the
 * newly created keystore. If null is specified, the alias previously set by setKeypairAlias
 * will be used.
 * @throws LaboException
 * @see #setKeystorePassword
 * @see #setKeypairAlias
 * @see #setPrivateKeyPassword
 */
//---------------------------------------------------------------------------

	public void setupKeystore (PhysicianKey p_PhysicianKey, String p_PrivateKeyPassword, LaboratoryInterface p_LaboManager) throws LaboException
	{
		PrivateKey		l_PrivateKey;
		Certificate[]	l_CertificateChain;
		
		// check parameter
		if (p_PhysicianKey == null 
				|| p_PhysicianKey.getPemPrivate() == null 
				|| p_PhysicianKey.getPemPublic() == null) 
			throw new LaboException(Integer.valueOf(
					  LaboException.ERROR_KEY_NOT_SET 
					| LaboException.ERROR_CERTIFICATE_NOT_SET));
		
		if (p_PrivateKeyPassword != null && p_PrivateKeyPassword.trim().length() == 0)
			p_PrivateKeyPassword	= null;
		
		if (p_PhysicianKey.isPersistent() && p_PrivateKeyPassword == null && m_PasswordEncrypter != null)
		{
			m_PrivateKeyPassword = m_PasswordEncrypter.decryptPassword(p_PhysicianKey.getPassword());
		}
		else
		{
			m_PrivateKeyPassword = p_PrivateKeyPassword;
		}
		
		if (m_PrivateKeyPassword == null)
			throw new LaboException(Integer.valueOf(
					LaboException.ERROR_PASSWORD_NOT_SET));
		
		m_Logger.log(Level.INFO, "Setting up keystore for key " + p_PhysicianKey.getLabel());
		
		try
		{
			//---------------------------------------------------------------------------
			// Step 1
			// Create a new PKCS#12 keystore to store public and private key into.
			
			try
			{
				m_Keystore = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
				m_Keystore.load(null, null);
			}
			catch (Exception e)
			{
				m_Logger.log(Level.ERROR, "Couldn't create key store instance");
				throw new LaboException(LaboException.ERROR_CREATING_KEYSTORE_INSTANCE);
			}
			
			//---------------------------------------------------------------------------
			// Step 2
			// Load the Certificate specified by p_CertificateFile. 
			
			l_CertificateChain = new java.security.cert.Certificate[1];
			l_CertificateChain[0] = this.getCertificate(p_PhysicianKey.getPemPublic(), p_LaboManager);
			
			//---------------------------------------------------------------------------
			// Step 3
			// Load the private key specified by p_PrivateKeyFile. If loading private
			// key fails, then we're returning right away with null.
			
			l_PrivateKey = this.getPrivateKey(p_PhysicianKey.getPemPrivate());
			
			//---------------------------------------------------------------------------
			// Step 4
			// Store Certificate and Private Key in keystore and tag keypair with 
			// specified keypair alias.
			
			try
			{
				m_Keystore.setKeyEntry(p_PhysicianKey.getLabel(), l_PrivateKey, m_PrivateKeyPassword.toCharArray(), l_CertificateChain);
			}
			catch (KeyStoreException e)
			{
				m_Logger.log(Level.ERROR, "Couldn't store keys in key store", e);
				throw new LaboException(LaboException.ERROR_STORING_KEYS);
			}
		}
		catch (LaboException p_Exception)
		{
			m_Logger.log(Level.WARN, "Caught exception during keystore setup!", p_Exception);
			throw p_Exception;
		}
	}

//---------------------------------------------------------------------------
/**
 * Reads the S/MIME encoded message from stream specified by p_EncryptedStream. The public and private key pair required
 * to decrypt the S/MIME message will be retrieved from the previously setup
 * keystore, using the keypair alias specified by p_KeypairAlias.
 * 
 * @param p_EncryptedData specifies the the S/MIME encoded stream to be decrypted.
 * @param p_Keystore specifies the keystore containing the public and private key pair required to decrypt
 * the S/MIME encoded message.
 * @return a byte array containing the decrypted content of the specified S/MIME message. In case an error occurred, null will be returned.
 * @throws Exception
 */
//---------------------------------------------------------------------------

public byte[] decrypt (byte[] p_EncryptedData, String p_KeypairAlias) throws Exception                          
{
    ByteArrayInputStream		l_EncryptedStream;
	PrivateKey      			l_PrivateKey;
    Properties 					l_Properties; 
    Session    					l_Session;
    MimeMessage 				l_MimeMessage;
    SMIMEEnveloped 				l_SmimeEnveloped;
    RecipientInformationStore 	l_RecipientInfo;
    RecipientInformation 		l_Recipient;

    Vector<?>					l_Recipients;
    byte []         			l_Decrypted;
    
    Exception       			l_Exception;
    
    // Use previously set passwords and alias if corresponding parameters have been
    // omitted, i.e. were set to null.
    
    if (m_Keystore == null) 
        { 
        m_Logger.log (Level.WARN,"No keystore available");
        
        l_Exception = new DecryptException(ImportInterface.NO_KEYSTORE_SET);
//        l_Exception = new SmimeDecoderException ("NoKeystore"); 
        throw l_Exception;
        }
    
    //if (p_KeystorePassword == null) 	{ p_KeystorePassword 	= m_KeystorePassword; }
    //if (p_KeypairAlias == null)     	{ p_KeypairAlias     	= m_KeypairAlias; }
    //if (p_PrivateKeyPassword == null) 	{ p_PrivateKeyPassword 	= m_PrivateKeyPassword; }
    
    //---------------------------------------------------------------------------
    // Step 1 
    // Retrieve the Private Key from specified Keystore. If retrieval operation
    // fails, we'll return right away with null.

    // m_PrivateKeyPassword = p_PrivateKeyPassword;
    
    try {
        l_PrivateKey = (PrivateKey) m_Keystore.getKey (p_KeypairAlias, m_PrivateKeyPassword.toCharArray());

        if (l_PrivateKey == null) 
            { 
            m_Logger.log (Level.WARN,"Failed to retrieve private key from keystore. Please check private key password");
                
//            l_Exception = new SmimeDecoderException ("PrivateKeyFailed");
            l_Exception = new DecryptException("No key set for the result", ImportInterface.NO_KEY_SET);
            throw l_Exception;
            }

        //---------------------------------------------------------------------------
        // Step 2  
        // Open a mail session in order access MIME message part. 

        l_Properties = System.getProperties();
        l_Session    = Session.getDefaultInstance (l_Properties, null);

        //---------------------------------------------------------------------------
        // Step 2.1  
        // Retrieve encrypted MIME message part from specified S/MIME file.

        l_EncryptedStream = new ByteArrayInputStream (p_EncryptedData);       
        l_MimeMessage    = new MimeMessage (l_Session, l_EncryptedStream);            
        l_SmimeEnveloped = new SMIMEEnveloped (l_MimeMessage);

        //---------------------------------------------------------------------------
        // Step 2.2  
        // Retrieve recipient information from S/MIME encoded file.

        l_RecipientInfo = l_SmimeEnveloped.getRecipientInfos();
        l_Recipients    = new Vector<Object>(l_RecipientInfo.getRecipients());
        l_Recipient     = (RecipientInformation) l_Recipients.elementAt(0);

        //---------------------------------------------------------------------------
        // Step 3  
        // The decryption process itself

        try {
        	l_Decrypted = l_Recipient.getContent (l_PrivateKey, BouncyCastleProvider.PROVIDER_NAME);
        	}
        catch (Exception p_Exception)
        	{
        	throw new DecryptException("Decryption failed!", p_Exception, ImportInterface.UNABLE_TO_DECRYPT, p_EncryptedData);
        	}
        }
    catch (DecryptException p_Exception)
    	{
    	throw p_Exception;
    	}
    catch (Exception p_Exception)
        {
        m_Logger.log (Level.WARN,"Failed to decrypt content", p_Exception);
            
        l_Exception = new DecryptException("Failed to decrypt content", p_Exception, ImportInterface.UNABLE_TO_DECRYPT, p_EncryptedData);
        throw l_Exception;
       }
    
    return l_Decrypted; 
    }

//---------------------------------------------------------------------------

public byte[] verify (byte[]  p_SignedData, LaboratoryCertificate p_Certificate, LaboratoryInterface p_LaboManager) throws LaboException                          
    {
    ByteArrayInputStream		l_SignedStream;
    ByteArrayOutputStream 		l_BodyPartContent;
    Properties 					l_Properties; 
    Session    					l_Session;
    MimeMessage 				l_MimeMessage;
    SMIMESigned 				l_SmimeSigned;
    MimeBodyPart				l_BodyPart;        
//   Matcher 					l_CharsetMatcher;
//   String                      l_Content;
//   String                      l_Charset;
    byte []                     l_Verified; 
//    byte []						l_Content;
    
    X509Certificate             l_Certificate;
    
    SignerInformationStore      l_SignerInfo;
    SignerInformation           l_Signer;
    Vector<?>                   l_Signers;
    
     
    if ((p_SignedData == null) || ( p_Certificate == null) || (!p_Certificate.getTrusted()))
    	return p_SignedData;
    
    l_Verified  = null;
//    l_Content   = null;
//    l_Charset 	= c_DefaultCharset;
    
    try {
        //---------------------------------------------------------------------------
        // Step 1  
        // Open a mail session in order access MIME message part. 

        l_Properties = System.getProperties();
        l_Session    = Session.getDefaultInstance (l_Properties, null);

        //---------------------------------------------------------------------------
        // Step 2  
        // Retrieve MIME message part from specified S/MIME file.
       
        l_SignedStream = new ByteArrayInputStream (p_SignedData);       
        l_MimeMessage  = new MimeMessage (l_Session, l_SignedStream);            
        l_BodyPartContent = new ByteArrayOutputStream ();
        
        //---------------------------------------------------------------------------
        // Step 3
        // make sure this was a multipart/signed message - there should be
        // two parts as we have one part for the content that was signed and
        // one part for the actual signature. 
        
        if (l_MimeMessage.isMimeType ("multipart/signed"))
            {
            l_SmimeSigned = new SMIMESigned ((MimeMultipart)l_MimeMessage.getContent());
            l_BodyPart    = l_SmimeSigned.getContent ();
             
            this.copyData (l_BodyPart.getInputStream(), l_BodyPartContent);
             // l_Content     = (String) l_BodyPart.getContent ();
             
        //---------------------------------------------------------------------------
        // Step 4
        // Retrieve the content type of the mime body part and match it against
        // the text/plain; charset=(.*) pattern in order to retrieve the charset
        // of the content
                     
//            l_CharsetMatcher = c_CharsetPattern.matcher (l_BodyPart.getContentType ());
//            if (l_CharsetMatcher.matches()) 
//            	 l_Charset = l_CharsetMatcher.group (1);
 
        //---------------------------------------------------------------------------
        // Step 5
        // Load the Certiciate specified by p_CertificateFile. 
     
        l_Certificate = this.getCertificate (p_Certificate.getPemData(), p_LaboManager);
            
        //---------------------------------------------------------------------------
        // Step 6
        // Retrieve Signer Block
        
        l_SignerInfo = l_SmimeSigned.getSignerInfos();
        l_Signers    = new Vector(l_SignerInfo.getSigners());
        l_Signer     = (SignerInformation) l_Signers.elementAt (0);
        
        try {
            if (!l_Signer.verify (l_Certificate,BouncyCastleProvider.PROVIDER_NAME))
                {
                m_Logger.log (Level.WARN,"Signature does not match certificate signature");
                throw new LaboException (LaboException.ERROR_CREATING_KEYSTORE_INSTANCE, "SignatureFailed");
                }
        	}
        catch (CertificateExpiredException e)
        	{
        	m_Logger.log(Level.WARN, "Certificate expired");
        	}
        }
        
        //---------------------------------------------------------------------------
        // Step 7
        // Content passed signature check. We may now retrieve the verified content,
        // making sure it is in the right charset (if specified)

        l_Verified = l_BodyPartContent.toByteArray();
        
//        if (l_Content != null) 
//            {
//            if (l_Charset.length() > 0)
//                 l_Verified = l_Content.getBytes (l_Charset);
//            else l_Verified = l_Content.getBytes ();
//            }
        }
    catch (LaboException p_Exception)
        {
        throw p_Exception;
        }
    catch (Exception p_Exception)
        {
        m_Logger.log (Level.WARN, "Failed to verify signature",p_Exception);
        throw new LaboException (p_Exception, LaboException.ERROR_VERIFYING_DATA, "Verification failed");
        }
    
    return l_Verified;
    }

//---------------------------------------------------------------------------

public int getErrors ()
	{
	return m_Errors;
	}


public boolean isError (int p_ErrorToTest)
	{
	return (m_Errors & p_ErrorToTest) == p_ErrorToTest;
	}

//---------------------------------------------------------------------------

public char[] getPassword()
	{
	return (m_PrivateKeyPassword != null)?m_PrivateKeyPassword.toCharArray():null;
	}

//---------------------------------------------------------------------------

protected List<X509Certificate> readChain (String caString) throws LaboException
{
	String[]				chainStrings	= caString.split(CERT_SEPARATOR);
	List<X509Certificate>	chain			= new LinkedList<X509Certificate>();
	X509Certificate			cert;
	
	
	for (String certString : chainStrings) 
	{
		if (certString == null || certString.trim().length() == 0)
			continue;
		
		cert = (X509Certificate) parsePEMData(certString.getBytes());
		chain.add(cert);
	}
	
	return chain;
}

//***************************************************************************
//* End of Class                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
	}
