/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 */

package de.nece.ccrle.sandbox;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathBuilderResult;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorResult;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.CRL;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.cert.X509CertSelector;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.X509CRLSelector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.logging.Level;
import javax.security.auth.x500.X500Principal;


/**
 * A manager for X.509 trustmaterial.
 *
 *
 * @author Greg Kohring
 */
final public class X509TrustManager extends TrustManager {

    private static Allx509Constants x509 = new Allx509Constants();
    private KeyStore trustStore = null;
    private Set trusted = null;
    private Set crls = null;
    private CertStore crlStore = null;
    private Provider securityProvider = null;
    private boolean revocationEnabled = true;
    private static Logger logger =
                       Logger.getLogger( X509TrustManager.class.getName() );

    private static final int CRL_SIGN = 6;

    /**
     * Creates a new trust manager from the specified trust material.
     *
     * @param trustStore a java <code>Keystore</code> consisting of 
     *      <code>TrustAnchor</code>s. Normally, these will be CA 
     *      root certificates, but they can be any trusted certificate.
     * @param crls a <code>Set</code> containing <code>CRL</code>s corresponding
     *      to the trusted CAs. If <code>null</code> or empty then no 
     *      CRL checking is done.
     * @throws InvalidAlgorithmParameterException if <em>trustStore</em> 
     *      does not contain at least one trusted certificate.
     * @throws KeyStoreException if an error occurs while extracting the
     *      trusted certificates from the <code>Keystore</code>.
     * @throws NullPointerException if <em>trusted</em> 
     *      is <code>null</code>.
     */
    public X509TrustManager( KeyStore trustStore, Collection crls ) 
                    throws  KeyStoreException, 
                                InvalidAlgorithmParameterException {

        this.trustStore = trustStore;

        trusted = new HashSet();

        addTrustedCerts( trustStore );

        init( crls );
    }

    /**
     * Creates a new trust manager from the specified trust material.
     *
     * @param trusted a <code>Set</code> consisting of 
     *      <code>TrustAnchor</code>s. Normally, these will be CA 
     *      root certificates, but they can be any trusted certificate.
     * @param crls a <code>Set</code> containing <code>CRL</code>s corresponding
     *      to the trusted CAs. If <code>null</code> or empty then no 
     *      CRL checking is done.
     * @throws InvalidAlgorithmParameterException if <em>trusted</em> 
     *      does not contain at least one trusted certificate.
     * @throws NullPointerException if <em>trusted</em> 
     *      is <code>null</code>.
     */
    public X509TrustManager( Collection trusted, Collection crls ) 
                    throws  InvalidAlgorithmParameterException {

        this.trusted = new HashSet( trusted );

        if ( this.trusted.isEmpty() ){
            throw new IllegalArgumentException( "trusted is empty" );
        }

        init( crls );
    }

    // performs some initializations
    private void init( Collection crls ){

        if ( crls == null ){
            this.crls = new HashSet();
        } else {
            this.crls = new HashSet( crls );
        }

        securityProvider = Security.getProviders()[0];

        // Check the CRLs to make sure they were actually signed by one of
        // the CAs. Those which were not properly signed are removed.

        checkCRLSignatures( this.crls, trusted );

        // Create the CertStore containg for the CRLs

        try{
            CollectionCertStoreParameters crlCCSP =
                            new CollectionCertStoreParameters( this.crls );
            crlStore = CertStore.getInstance( "Collection", crlCCSP,
                                                        securityProvider );
        } catch ( NoSuchAlgorithmException nsae ){
            logger.warning( nsae.getMessage() );
        } catch ( InvalidAlgorithmParameterException iape ){
            logger.warning( iape.getMessage() );
        }

    }

    /**
     * Instruct the TrustManager to use the specified security provider.
     * If no security provider has been set, then the default provider is
     * used.
     *
     * @param securityProvider the security <code>Provider</code> to be used.
     * @throws IllegalArgumentException if <em>securityProvider</em> is
     *      <code>null</code>.
     */
    public void setSecurityProvider( Provider securityProvider ){

        if ( securityProvider == null ){
            throw new IllegalArgumentException( "securityProvider == null!" );
        }

        this.securityProvider = securityProvider;

        Security.addProvider( this.securityProvider );

        // Re-create the CertStore using the new security provider

        try{
            CollectionCertStoreParameters crlCCSP =
                            new CollectionCertStoreParameters( this.crls );
            crlStore = CertStore.getInstance( "Collection", crlCCSP,
                                                        securityProvider );
        } catch ( NoSuchAlgorithmException nsae ){
            logger.warning( nsae.getMessage() );
        } catch ( InvalidAlgorithmParameterException iape ){
            logger.warning( iape.getMessage() );
        }

    }
                                                                                
    /**
     * Retrieves the current security provider.
     *
     * @return the security <code>Provider</code> currently in use.
     */
    public Provider getSecurityProvider(){
        return securityProvider;
    }
                                                                                

    /**
     * Checks whether or not the specified certificate chain is trusted for
     * the purpose of code signing. If the chain is trusted for code signing
     * then this method returns a list of the valid signing chains found,
     * otherwise it will throw a <code>CertificateException</code>.
     * <p>
     * To be trusted for code signing, the certificate chain must
     * be validated against the trust store managed by this trust manager.
     * Furthermore, the code signing certificate itself must either contain
     * the extension,
     * <em>Extended Key Usage (OID 2.5.29.37)</em>, along with the
     * purpose, <em>Code Signing (OID 1.3.6.1.5.5.7.3.3)</em>, or contain
     * the extension <em>Netscape cert type (OID 2.16.840.1.113730.1.1)</em>,
     * with the <em>Object-signing</em> bit set in the extension value.
     * <p>
     * NOTE: The <em>Netscape cert type</em> extension is deprecated and this
     * functionality is provided only to support 
     * backwards compatibility for older Netscape Object signing certificates.
     * New signing certificates should use the  <em>Extended Key Usage </em>
     * extension.
     * 
     *
     * @param chain an <code>array</code> of certificates to check. 
     *      The chain must be orderd using the standard X.509 convention,
     *      i.e., the first
     *      certificate in the chain is the code signing certificate.
     * @return a <code>List</code> containing a
     *      {@link java.security.cert.PKIXCertPathBuilderResult} object 
     *      for each of
     *      the valid signing chains found within the specified chain.
     * @throws CertificateException if the chain is not trusted for code
     *      signing.
     */
    public synchronized List checkCodeSigningTrusted( Certificate[] chain ) 
                                        throws CertificateException {

        CertPathBuilder pathBuilder = null;

        // Make sure the chain is not empty

        if ( chain == null ) {
            throw new CertificateException( "Certificate chain is null!" );
        } else if ( chain.length == 0 ) {
            throw new CertificateException( "Chain has no certificates!" );
        }

        // Test the chain to see if consists of a single chain of certificates
        // or, if there are multiple certificate chains 

        List chains = subChains( chain );

        List results = new ArrayList();

        boolean validChainFound = false;
        String msg = "";


        // Create a SignerSelector to help select the allowed signing
        // certificates.

        SignerSelector signerSelector = new SignerSelector();

        PKIXCertPathBuilderResult  builderResult = null;

        // Create a pkix path builder

        try {
            pathBuilder = CertPathBuilder.getInstance( "PKIX", 
                                                        securityProvider );
        } catch ( NoSuchAlgorithmException nsae ){
            throw new CertificateException( nsae.getMessage() );
        }

        // For each subchain, attempt to build a certificate path from the
        // one of the trust anchors to the signing certificate
        for ( int c = 0; c < chains.size(); c++){

            List subChain = (List) chains.get( c );

            // Create the PKIX building parameters

            PKIXBuilderParameters signingParms = null;

            try { 
                signingParms =  
                        new PKIXBuilderParameters( trusted, signerSelector );
            } catch ( InvalidAlgorithmParameterException iape ){
                throw new CertificateException( iape.getMessage() );
            }

            // Create CertStores containing the certificates in
            // the chain

            CertStore chainStore = null;
            try{
                CollectionCertStoreParameters chainCCSP =
                            new CollectionCertStoreParameters( subChain );
                chainStore = CertStore.getInstance( "Collection", chainCCSP,
                                                        securityProvider );
            } catch ( NoSuchAlgorithmException nsae ){
                msg += " NSAE message: " + nsae.getMessage() + "  ";
                continue;
            } catch ( InvalidAlgorithmParameterException iape ){
                msg += " IAPE (ccsp) message: " + iape.getMessage() + "  ";
                continue;
            }

            // Add the cert stores

            signingParms.addCertStore( crlStore );
            signingParms.addCertStore( chainStore );

            // Add our custom revocation checker

            signingParms.setRevocationEnabled( false );
            signingParms.addCertPathChecker( new CRLChecker() );

            // build the path

            try {
                builderResult = (PKIXCertPathBuilderResult) 
                                         pathBuilder.build( signingParms );
            } catch ( CertPathBuilderException cpbe ){
                msg += " CPBE message: " + cpbe.getMessage() + "  ";
                continue;
            } catch ( InvalidAlgorithmParameterException iape ){
                msg += "IAPE (pcpbr) message: " + iape.getMessage() + "  ";
                continue;
            }

            results.add( builderResult );
            validChainFound = true;
        }


        if ( !validChainFound ){
            throw new CertificateException( 
                     "No valid certificate path found. Reason:  "  + msg );
        } else if ( !msg.equals( "" ) ){
            logger.finest( msg );
        }

        return results;

    }
        
    /**
     * Checks whether or not the specified certificate chain is trusted based
     * upon the trust anchors known to this trust manager.
     *
     * @param chain an <code>array</code> of certificates to check.
     * @return a <code>List</code> containing a
     *      {@link java.security.cert.PKIXCertPathValidatorResult} object 
     *      for each of
     *      the trusted chains found within the specified chain.
     * @throws CertificateException if the chain is not trusted.
     */
    public synchronized List checkTrusted( Certificate[] chain ) 
                                        throws CertificateException {

        // Make sure the chain is not empty

        if ( chain == null ) {
            throw new CertificateException( "Certificate chain is null!" );
        } else if ( chain.length == 0 ) {
            throw new CertificateException( "Chain has no certificates!" );
        }

        // Test the chain to see if consists of a single chain of certificates
        // or, if there are multiple certificate chains 

        List chains = subChains( chain );

        List results = new ArrayList();

        boolean validChainFound = false;
        String msg = "";

        // Create a CertPathValidator instance
        CertPathValidator cpv = null;
        try {
            cpv = CertPathValidator.getInstance( "PKIX", securityProvider );
        } catch ( NoSuchAlgorithmException nsae ){
            throw new CertificateException( "Algorithm error: " 
                                                    + nsae.getMessage() );
        }

        // Create a certificate factory

        CertificateFactory cf = null;

        // instantiate a CertificateFactory for X.509

        cf = CertificateFactory.getInstance( "X.509", securityProvider );

        CertPathValidatorResult validatorResult = null;

        // For each subchain, attempt to build a certificate path from the
        // one of the trust anchors to the signing certificate
        for ( int c = 0; c < chains.size(); c++){

            List subChain = (List) chains.get( c );

            // Create the PKIX building parameters

            PKIXParameters pkixParms = null;

            try { 
                pkixParms = new PKIXParameters( trusted );
            } catch ( InvalidAlgorithmParameterException iape ){
                throw new CertificateException( iape.getMessage() );
            }

            // Add the cert stores

            pkixParms.addCertStore( crlStore );

            // Enable revocation checking

            pkixParms.setRevocationEnabled( false );
            pkixParms.addCertPathChecker( new CRLChecker() );


            // Now try to validate the certificate path

            CertPath certPath = null;
            try {
                certPath = cf.generateCertPath( subChain );
            } catch ( CertificateException ce ) {
                msg += "CE messge: " + ce.getMessage() + "  ";
                continue;
            }

            try {
                validatorResult = cpv.validate( certPath, pkixParms ); 
            } catch ( CertPathValidatorException cpve ) {
                // Determine the certificate which caused validation exception

                if ( cpve.getCertPath() == null ){
                    msg += "CPVE message: " + cpve.getMessage() + "  ";
                    continue;
                }

                List certs = cpve.getCertPath().getCertificates();
                int index = cpve.getIndex();
                X509Certificate failed = null;
                if ( index != -1 ){
                    failed = (X509Certificate) certs.get( cpve.getIndex() );
                }

                // If validation failed because the certificate is not a 
                // a CA, i.e., does not have the CA critical extension (like
                // Verisign CA certificates) then check if it is among the list
                // of trusted certificates.  (CA certificates are not
                // supposed to be part of the signing chain, but many people
                // mistakenly add them, so we have to accept it.)

                if ( !isTrustAnchor( failed ) ) {
                    String msg2 = "Validation failure, cert[" 
                                            + cpve.getIndex() + "]: ";
                    if ( failed != null ) {
                        msg2 += failed.getSubjectDN().getName() + "  " ;
                    }

                    msg += msg2 + cpve.getMessage() + "  ";
                    continue;

                } else {

                // Remove the trust anchor from the certificate path
                // and continue testing the reduced path.

                    if ( cpve.getIndex() > 0 ){
                        List newChain = new ArrayList( cpve.getIndex() ); 
                        for ( int i = 0; i < cpve.getIndex() ; i++ ){
                            newChain.add( subChain.get( i ) );
                        }
                        try {
                            certPath = cf.generateCertPath( newChain );
                            List newResults = checkTrusted(
                                       (Certificate[]) newChain.toArray());
                            validatorResult = (CertPathValidatorResult)
                                                         newResults.get(0);
                        } catch ( CertificateException ce ) {
                            msg += ce.getMessage() + "  ";
                            continue;
                        }
                    } else {
                        msg += cpve.getMessage() + "  ";
                        continue;
                    }
                }
            } catch ( InvalidAlgorithmParameterException iape ) {
                msg += "IAPE message: " + iape.getMessage() + "  ";
                continue;
            }

            results.add( validatorResult );
            validChainFound = true;
        }


        if ( !validChainFound ){
            throw new CertificateException( 
                     "No valid certificate path found. Reason:  "  + msg );
        } else if ( !msg.equals( "" ) ){
            logger.finest( msg );
        }

        return results;

    }

    // A collection of x509 constants
    private static class Allx509Constants implements X509Constants {
    }


    // Break a chain into its constituent chains
    private List subChains( Certificate[] chain ){

        List chains = new ArrayList();
        List subChain = new ArrayList();

        for ( int c = 0; c <  chain.length - 1 ; c++ ){
            X509Certificate cert = (X509Certificate) chain[c];
            X509Certificate ca = (X509Certificate) chain[c+1];

            subChain.add( cert );

            X500Principal issuer  = cert.getIssuerX500Principal();
            X500Principal subject = ca.getSubjectX500Principal();

            if ( !subject.equals( issuer ) ){

                chains.add( subChain );
                subChain = new ArrayList();

            }
        }

        subChain.add( chain[chain.length - 1] );
        chains.add( subChain );

        return chains;
    }

    /**
     * Checks the given set of crls against the set of trust anchors.
     * CRLS whose signature cannot be verified against one of
     * the trusted CAs are removed from the set.
     *
     * @param crls a <code>Set</code> containing <code>X509CRL</code> objects.
     * @param trusted a <code>Set</code> containing 
     *      <code>TrustAnchor</code> objects.
     */
    private boolean checkCRLSignatures( Set crls, Set trusted ){

        boolean crlSetUnchanged = true;

        // loop over crls
        for ( Iterator crlIt = crls.iterator(); crlIt.hasNext(); ){

            boolean verified = false;

            X509CRL x509CRL = (X509CRL) crlIt.next();
            X500Principal issuer = x509CRL.getIssuerX500Principal();
            String issuerName = issuer.getName();

            // Check the CRL's date

            Date now = new Date();

            if ( now.after( x509CRL.getNextUpdate() ) ){
                logger.warning( "CRL from " + issuer + " is out of date!" 
                        + " To turn on automatic CRL downloads,"
                        + " see your GEMSS support person." );
            }

            // loop over the trust anchors
            for ( Iterator trustIt = trusted.iterator(); trustIt.hasNext(); ){
                TrustAnchor trustedCA = (TrustAnchor) trustIt.next();

                // A trust anchor has either a ca Name and public key or a
                // ca certificate, but not both.

                String caName = trustedCA.getCAName();
                PublicKey caPublicKey = trustedCA.getCAPublicKey();

                if ( caName == null ){
                    X509Certificate ca = trustedCA.getTrustedCert();
                    caName = ca.getSubjectX500Principal().getName();
                    caPublicKey = ca.getPublicKey();

                    // Make sure the CA is allowed to sign a CRL
                    if ( issuerName.equals( caName ) ){
                        try{
                            boolean[] keyusage = ca.getKeyUsage();
                                                                                
                            if ( keyusage != null && 
                                 ( keyusage.length < 7 || 
                                                    !keyusage[CRL_SIGN]) ) {
                                logger.warning( 
                                    "Certificate not valid for CRL signing : "
                                    + ca.getSubjectX500Principal() );
                            } else {
                                x509CRL.verify( caPublicKey );
                                verified = true;
                            }
                        } catch ( Exception e ){
                            // if there is an exception we discard this CRL
                            logger.warning( e.getMessage() );
                        }
                    }

                }
            }

            if ( !verified ) {
                crlIt.remove();
                crlSetUnchanged = false;
                logger.warning( "CRL not verified: " + x509CRL.getIssuerDN() );
            }

        }

        return crlSetUnchanged;
    }

    /**
     * Checks whether or not the specified certificate is a one
     * of the trust anchors.
     *
     * @param cert an <code>X509Certificate</code> 
     *      whose membership in the set of <em>anchors</em> is to be checked.
     * @return a <code>true</code> if the specified certificate is a member
     *      of <em>anchors</em> and <code>false</code>otherwise.
     */
    private boolean isTrustAnchor( X509Certificate cert ){
        boolean isATrustAnchor = false;
        for( Iterator taIt = trusted.iterator(); taIt.hasNext(); ){
            TrustAnchor ta = (TrustAnchor) taIt.next();
            X509Certificate trusted = ta.getTrustedCert();
            if ( cert.equals( trusted) ){
                isATrustAnchor = true;
                break;
            }
        }

        return isATrustAnchor;
    }



    /**
     * Checks whether or not the specified certificate is 
     * within its period of validity
     *
     * @param x509Cert an <code>X509Certificate</code> whose validity
     *      is to be checked.
     * @throws CertificateException if the certificate 
     *      is not currently valid.
     */
    public void checkValidity( X509Certificate x509Cert ) 
                                    throws CertificateException {
        try{
            x509Cert.checkValidity();
        } catch ( CertificateExpiredException cee ){
            throw new CertificateException( "Certificate with DN: "
                  + x509Cert.getSubjectDN().getName()
                  + "  not valid after: " + x509Cert.getNotAfter() );
        } catch ( CertificateNotYetValidException cnyve ){
            throw new CertificateException( "Certificate with DN:  "
                  + x509Cert.getSubjectDN().getName()
                  + "  not valid before:  " + x509Cert.getNotBefore() );
        }
    }

    /**
     * Checks whether or not the specified certificate is listed as being
     * revoked on any of the CRLs known to this trust manager. If the
     * certificate is on any CRL then an exception is thrown.
     * <p>
     * The absence of an exception being thrown does not necessarily mean
     * that the specified certificate has not been revoked, it only 
     * means that it is not listed on any CRL available to this trust
     * manager.  The job of maintaining an up-to-date and complete 
     * collection of CRLs is outside the scope of the trust manager.
     *
     * @param x509Cert an <code>X509Certificate</code> 
     *      whose revocation status is to be checked.
     * @throws CertificateException if the certificate has been revoked.
     */
    public void checkRevoked( X509Certificate x509Cert )
                                    throws CertificateException{

        X500Principal certIssuer = x509Cert.getIssuerX500Principal();
        BigInteger serialNum = x509Cert.getSerialNumber();

        Date now = new Date();

        // Loop over all CRLs in this collection

        for ( Iterator crlIt = crls.iterator(); crlIt.hasNext(); ){
            X509CRL crl = (X509CRL) crlIt.next();
            X500Principal crlIssuer = crl.getIssuerX500Principal();

            if ( crlIssuer.equals( certIssuer ) ){

                X509CRLEntry entry = crl.getRevokedCertificate( serialNum );

                if ( ( entry != null ) &&
                     now.after( entry.getRevocationDate() ) ){

                    throw new CertificateException(
                          "Certificate: " +  x509Cert.getSubjectDN().getName() 
                           + " was revoked on: "
                           + entry.getRevocationDate() );
                }
            }
        }

    }


    // A class for selecting code signing certificates.
    private class SignerSelector extends X509CertSelector{

        public SignerSelector(){
            super();
        }

        // Match the certificate if it is a code signing certificate
        public boolean match( Certificate cert ){

            if ( !(cert instanceof X509Certificate ) ){
                return false;
            }

            X509Certificate x509Cert = (X509Certificate) cert;

            try{
            //    checkCriticalExtension( x509Cert, x509.EXTENDED_KEY_USAGE );
                checkExtendedKeyUsagePurpose( x509Cert, x509.CODE_SIGNING );
            } catch ( CertificateException ce ){
            // if it does not have the standard code signing extension, it
            // may have the Netscape Object signing extension

                try {
                    checkNetscapeSigning( x509Cert );
                } catch ( CertificateException ce2 ){
                    return false;
                }
            }

            return super.match( cert );
        }

    }

    /**
     * Checks whether or not the specified certificate has the specified
     * critical extension.
     *
     * @param x509Cert the <code>X509Certificate</code> to be checked. 
     * @param extension a <code>String</code> containing the desired
     *      extension, in OID format.
     * @throws CertificateException if the certificate does not contian the
     *      specified critical extension.
     */
    private void checkCriticalExtension( X509Certificate x509Cert, 
                        String extension ) throws CertificateException {

        Set critSet = x509Cert.getCriticalExtensionOIDs();
        boolean foundCritExtension = false;
        if ( ( critSet != null ) &&  !critSet.isEmpty() ) {
            for ( Iterator it = critSet.iterator(); it.hasNext(); ) {
                String oid = (String) it.next();
                if ( oid.equals( extension ) ) {
                    foundCritExtension = true;
                    break;
                }
            }
        }

        if ( !foundCritExtension ){
            throw new CertificateException( 
                "Certificate does not contain required critical extension: " + 
                                    x509.EXTENDED_KEY_USAGE );
        }

    }

    /**
     * Checks whether or not the extended key usage contains the
     * specified purpose.
     *
     * @param x509Cert the <code>X509Certificate</code> to be checked. 
     * @param purpose a <code>String</code> containing the desired
     *      purpose, in OID format.
     * @throws CertificateException if the certificate does not contian the
     *      specified purpose as an extended key usage.
     */
    private void checkExtendedKeyUsagePurpose( X509Certificate x509Cert, 
                            String purpose ) throws CertificateException{

        boolean foundPurpose = false;

        try{
            List ext = x509Cert.getExtendedKeyUsage();
            if ( ext != null ){
                for( Iterator it = ext.iterator(); it.hasNext(); ){
                    String usage = (String) it.next();
                    usage = usage.trim();
                    if ( usage.equals( purpose ) ){
                        foundPurpose = true;
                        break;
                    }
                }
            }
        } catch ( CertificateParsingException cpe){
            throw new CertificateException( "Parsing error: " +
                                                 cpe.getMessage() );
        }

        if ( !foundPurpose ){
            throw new CertificateException( "Certificate: " +
                    x509Cert.getSubjectDN() + " not valid for purpose: " + 
                                        purpose );
        }
    }

    /**
     * Checks whether or not the specified certificate is a Netscape
     * object signing certificate.  
     * <p>NOTE: The Nescape cert type extension is depricated.  This
     *          method is only included for backwork compatibility and
     *          is NOT very general as it assumes the only Netscape
     *          extension afforded the specified certificate is
     *          the "Object signing" extension.
     *
     * @param x509Cert the <code>X509Certificate</code> to be checked. 
     * @throws CertificateException if the certificate does not contian the
     *      specified critical extension or if the certificate
     *      is not currently valid.
     */
    private void checkNetscapeSigning( X509Certificate x509Cert ) 
                throws CertificateException {

        byte value[] = x509Cert.getExtensionValue( x509.NETSCAPE_CERT_TYPE );


        if ( ( value == null ) || ( value.length != 6 ) ){
            throw new CertificateException( "not netscape cert type" );
        }

        boolean foundExtension = true;

        if ( value[0] !=  4 ){
            foundExtension = false;
        }
        if ( value[1] !=  4 ){
            foundExtension = false;
        }
        if ( value[2] !=  3 ){
            foundExtension = false;
        }
        if ( value[3] !=  2 ){
            foundExtension = false;
        }

        // We do not need to check value[4] because it is bascially
        // irrevelant. It only records how many 0 bits the creator
        // thinks were needed to pad out value[5] to the full 8 bits.

        // Accept the certificate if it is marked for object signing.

        int data = value[5] & 0xff;

        if ( ( x509.OBJECT_SIGNING & data ) != x509.OBJECT_SIGNING ) {
                foundExtension = false; 
        }

        if ( !foundExtension ){
            throw new CertificateException( 
                "Certificate does not contain " +
                    "the Netscape object signing extension: " +
                x509Cert.getSubjectDN().getName() );
        }

    }

    boolean knownAs( Certificate cert, String alias ){

        if ( alias == null ) return false;

        if ( trustStore != null ){

            try{
                Certificate aliasCert = trustStore.getCertificate( alias );

                if ( ( aliasCert != null ) && aliasCert.equals( cert ) ){
                    return true;
                } else {
                    return false;
                }
            } catch ( KeyStoreException kse ){
                logger.finest( "keystore problem:  " + kse.getMessage() );
                return false;
            }
            
        } else {
            return false;
        }

    }

    void addTrustedCerts( KeyStore trustStore ) throws  KeyStoreException {
        for ( Enumeration aliasEnum = trustStore.aliases();
                            aliasEnum.hasMoreElements() ; ){
            String alias = (String) aliasEnum.nextElement();
            X509Certificate trustedCert = 
                    (X509Certificate) trustStore.getCertificate( alias );
            if ( trustedCert != null ){
                trusted.add( new TrustAnchor( trustedCert, null) );   
            }
        }
    }

    /**
     * A class for checking the revocation status of X509 certificates
     * during a PKIX validation step.
     */
    private class CRLChecker extends PKIXCertPathChecker {
        public CRLChecker(){
            super();
        }
        public void init( boolean forward ) throws CertPathValidatorException{
        }
        public boolean isForwardCheckingSupported(){
            return true;
        }
        public Set getSupportedExtensions(){
            return null;
        }
        public void check( Certificate cert, Collection unresolvedCritExts )
                    throws CertPathValidatorException {
            if ( cert instanceof X509Certificate ){
                try{
                    checkRevoked( (X509Certificate) cert );
                } catch ( CertificateException ce ){
                    throw new CertPathValidatorException( ce.getMessage() );
                }
            } else {
                throw new CertPathValidatorException( 
                                        "Not an X509 certificate" );
            }
        }
    }
}

