/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl.x509store;

import java.security.PublicKey;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.cert.X509Extension;

import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.HashSet;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERInteger;

/**
 * c: X509_STORE_CTX
 *
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public class StoreContext {
    public Store ctx;
    public int currentMethod;

    public X509AuxCertificate certificate;
    public List<X509AuxCertificate> untrusted;
    public List<X509CRL> crls;

    public VerifyParameter param;

    public List<X509AuxCertificate> otherContext;

    public static interface CheckPolicyFunction extends Function1 {
        public static final CheckPolicyFunction EMPTY = new CheckPolicyFunction(){
                public int call(Object arg0) {
                    return -1;
                }
            };
    }

    public Store.VerifyFunction verify;
    public Store.VerifyCallbackFunction verifyCallback;
    public Store.GetIssuerFunction getIssuer;
    public Store.CheckIssuedFunction checkIssued;
    public Store.CheckRevocationFunction checkRevocation;
    public Store.GetCRLFunction getCRL;
    public Store.CheckCRLFunction checkCRL;
    public Store.CertificateCRLFunction certificateCRL;
    public CheckPolicyFunction checkPolicy;
    public Store.CleanupFunction cleanup;

    public boolean isValid;
    public int lastUntrusted;
    
    public List<X509AuxCertificate> chain; //List<X509AuxCertificate>
    public PolicyTree tree;

    public int explicitPolicy;

    public int errorDepth;
    public int error;
    public X509AuxCertificate currentCertificate;
    public X509AuxCertificate currentIssuer;
    public java.security.cert.CRL currentCRL;

    public List<Object> extraData;

    /**
     * c: X509_STORE_CTX_set_depth
     */
    public void setDepth(int depth) { 
        param.setDepth(depth);
    }

    /**
     * c: X509_STORE_CTX_set_app_data
     */
    public void setApplicationData(Object data) {
        setExtraData(0,data);
    }

    /**
     * c: X509_STORE_CTX_get_app_data
     */
    public Object getApplicationData() {
        return getExtraData(0);
    }

    /**
     * c: X509_STORE_CTX_get1_issuer
     */
    public int getFirstIssuer(X509AuxCertificate[] issuer, X509AuxCertificate x) throws Exception { 
        Name xn = new Name(x.getIssuerX500Principal());
        X509Object[] s_obj = new X509Object[1];
        int ok = ctx == null ? 0 : getBySubject(X509Utils.X509_LU_X509,xn,s_obj);
        if(ok != X509Utils.X509_LU_X509) {
            if(ok == X509Utils.X509_LU_RETRY) {
                X509Error.addError(X509Utils.X509_R_SHOULD_RETRY);
                return -1;
            } else if (ok != X509Utils.X509_LU_FAIL) {
                return -1;
            }
            return 0;
        }
        X509Object obj = s_obj[0];
        if(this.checkIssued.call(this,x,((Certificate)obj).x509) != 0) {
            issuer[0] = ((Certificate)obj).x509;
            return 1;
        }

        int idx = X509Object.indexBySubject(ctx.objs,X509Utils.X509_LU_X509, xn);
        if(idx == -1) {
            return 0;
        }

        /* Look through all matching certificates for a suitable issuer */
        for(int i = idx; i < ctx.objs.size(); i++) {
            X509Object pobj = ctx.objs.get(i);
            if(pobj.type() != X509Utils.X509_LU_X509) {
                return 0;
            }
            if(!xn.isEqual((((Certificate)pobj).x509).getSubjectX500Principal())) {
                return 0;
            }
            if(this.checkIssued.call(this,x,((Certificate)pobj).x509) != 0) {
                issuer[0] = ((Certificate)pobj).x509;
                return 1;
            }
        }
        return 0;
    }

    public static List<X509AuxCertificate> ensureAux(Collection<X509Certificate> inp) {
        if (inp == null) {
            return null;
        }
        List<X509AuxCertificate> out = new ArrayList<X509AuxCertificate>();
        for(X509Certificate o : inp) {
            out.add(ensureAux(o));
        }
        return out;
    }

    public static List<X509AuxCertificate> ensureAux(X509Certificate[] inp) {
        if (inp == null) {
            return null;
        }
        List<X509AuxCertificate> o = new ArrayList<X509AuxCertificate>();
        for(X509Certificate c : inp) {
            o.add(ensureAux(c));
        }
        return o;
    }

    public static X509AuxCertificate ensureAux(X509Certificate i) {
        if (i == null) {
            return null;
        }
        if(i instanceof X509AuxCertificate) {
            return (X509AuxCertificate)i;
        } else {
            return new X509AuxCertificate(i);
        }
    }

    /**
     * c: X509_STORE_CTX_init
     */
    public int init(Store store, X509AuxCertificate x509, List<X509AuxCertificate> chain) { 
        int ret = 1;
        this.ctx=store;
        this.currentMethod=0;
        this.certificate=x509;
        this.untrusted=chain;
        this.crls = null;
        this.lastUntrusted=0;
        this.otherContext = null;
        this.isValid=false;
        this.chain = null;
        this.error=0;
        this.explicitPolicy=0;
        this.errorDepth=0;
        this.currentCertificate=null;
        this.currentIssuer=null;
        this.tree = null;

        this.param = new VerifyParameter();

        if(store != null) {
            ret = param.inherit(store.param);
        } else {
            param.flags |= X509Utils.X509_VP_FLAG_DEFAULT | X509Utils.X509_VP_FLAG_ONCE;
        }
        if(store != null) {
            verifyCallback = store.verifyCallback;
            cleanup = store.cleanup;
        } else {
            cleanup = Store.CleanupFunction.EMPTY;
        }

        if(ret != 0) {
            ret = param.inherit(VerifyParameter.lookup("default"));
        }

        if(ret == 0) {
            X509Error.addError(X509Utils.ERR_R_MALLOC_FAILURE);
            return 0;
        }

        if(store != null && store.checkIssued != null && store.checkIssued != Store.CheckIssuedFunction.EMPTY) {
            this.checkIssued = store.checkIssued;
        } else {
            this.checkIssued = defaultCheckIssued;
        }

        if(store != null && store.getIssuer != null && store.getIssuer != Store.GetIssuerFunction.EMPTY) {
            this.getIssuer = store.getIssuer;
        } else {
            this.getIssuer = new Store.GetIssuerFunction() {
                    public int call(Object arg1, Object arg2, Object arg3) throws Exception {
                        return ((StoreContext)arg2).getFirstIssuer((X509AuxCertificate[])arg1,(X509AuxCertificate)arg3);
                    }
                };
        }

        if(store != null && store.verifyCallback != null && store.verifyCallback != Store.VerifyCallbackFunction.EMPTY) {
            this.verifyCallback = store.verifyCallback;
        } else {
            this.verifyCallback = NullCallback;
        }

        if(store != null && store.verify != null && store.verify != Store.VerifyFunction.EMPTY) {
            this.verify = store.verify;
        } else {
            this.verify = internalVerify;
        }

        if(store != null && store.checkRevocation != null && store.checkRevocation != Store.CheckRevocationFunction.EMPTY) {
            this.checkRevocation = store.checkRevocation;
        } else {
            this.checkRevocation = defaultCheckRevocation;
        }

        if(store != null && store.getCRL != null && store.getCRL != Store.GetCRLFunction.EMPTY) {
            this.getCRL = store.getCRL;
        } else {
            this.getCRL = defaultGetCRL;
        }

        if(store != null && store.checkCRL != null && store.checkCRL != Store.CheckCRLFunction.EMPTY) {
            this.checkCRL = store.checkCRL;
        } else {
            this.checkCRL = defaultCheckCRL;
        }

        if(store != null && store.certificateCRL != null && store.certificateCRL != Store.CertificateCRLFunction.EMPTY) {
            this.certificateCRL = store.certificateCRL;
        } else {
            this.certificateCRL = defaultCertificateCRL;
        }

        this.checkPolicy = defaultCheckPolicy;

        this.extraData = new ArrayList<Object>();
        this.extraData.add(null);this.extraData.add(null);this.extraData.add(null);
        this.extraData.add(null);this.extraData.add(null);this.extraData.add(null);
        return 1;
    } 

    /**
     * c: X509_STORE_CTX_trusted_stack
     */
    public void trustedStack(List<X509AuxCertificate> sk) {
        otherContext = sk;
        getIssuer = getIssuerStack;
    }

    /**
     * c: X509_STORE_CTX_cleanup
     */
    public void cleanup() throws Exception {
        if(cleanup != null && cleanup != Store.CleanupFunction.EMPTY) {
            cleanup.call(this);
        }
        param = null;
        tree = null;
        chain = null;
        extraData = null;
    } 

    /**
     * c: find_issuer
     */
    public X509AuxCertificate findIssuer(List<X509AuxCertificate> sk, X509AuxCertificate x) throws Exception {
        for(X509AuxCertificate issuer : sk) {
            if(checkIssued.call(this,x,issuer) != 0) {
                return issuer;
            }
        }
        return null;
    }

    /**
     * c: X509_STORE_CTX_set_ex_data
     */
    public int setExtraData(int idx,Object data) { 
        extraData.set(idx,data);
        return 1; 
    } 

    /**
     * c: X509_STORE_CTX_get_ex_data
     */
    public Object getExtraData(int idx) { 
        return extraData.get(idx); 
    }

    /**
     * c: X509_STORE_CTX_get_error
     */
    public int getError() { 
        return error;
    }

    /**
     * c: X509_STORE_CTX_set_error
     */
    public void setError(int s) {
        this.error = s;
    } 

    /**
     * c: X509_STORE_CTX_get_error_depth
     */
    public int getErrorDepth() { 
        return errorDepth; 
    } 

    /**
     * c: X509_STORE_CTX_get_current_cert
     */
    public X509AuxCertificate getCurrentCertificate() { 
        return currentCertificate; 
    }

    /**
     * c: X509_STORE_CTX_get_chain
     */
    public List<X509AuxCertificate> getChain() { 
        return chain; 
    } 

    /**
     * c: X509_STORE_CTX_get1_chain
     */
    public List<X509AuxCertificate> getFirstChain() { 
        if(null == chain) {
            return null;
        }
        return new ArrayList<X509AuxCertificate>(chain); 
    } 

    /**
     * c: X509_STORE_CTX_set_cert
     */
    public void setCertificate(X509AuxCertificate x) {
        this.certificate = x;
    }

    public void setCertificate(X509Certificate x) {
        this.certificate = ensureAux(x);
    }

    /**
     * c: X509_STORE_CTX_set_chain
     */
    public void setChain(List<X509Certificate> sk) {
        this.untrusted = ensureAux(sk);
    }

    public void setChain(X509Certificate[] sk) {
        this.untrusted = ensureAux(sk);
    }

    /**
     * c: X509_STORE_CTX_set0_crls
     */
    public void setCRLs(List<X509CRL> sk) {
        this.crls = sk;
    } 

    /**
     * c: X509_STORE_CTX_set_purpose
     */
    public int setPurpose(int purpose) { 
        return purposeInherit(0,purpose,0);
    }

    /**
     * c: X509_STORE_CTX_set_trust
     */
    public int setTrust(int trust) { 
        return purposeInherit(0,0,trust);
    }

    private void resetSettingsToWithoutStore() {
        ctx = null;
        this.param = new VerifyParameter();
        this.param.flags |= X509Utils.X509_VP_FLAG_DEFAULT | X509Utils.X509_VP_FLAG_ONCE;
        this.param.inherit(VerifyParameter.lookup("default"));
        this.cleanup = Store.CleanupFunction.EMPTY;
        this.checkIssued = defaultCheckIssued;
        this.getIssuer = new Store.GetIssuerFunction() {
                public int call(Object arg1, Object arg2, Object arg3) throws Exception {
                    return ((StoreContext)arg2).getFirstIssuer((X509AuxCertificate[])arg1,(X509AuxCertificate)arg3);
                }
            };
        this.verifyCallback = NullCallback;
        this.verify = internalVerify;
        this.checkRevocation = defaultCheckRevocation;
        this.getCRL = defaultGetCRL;
        this.checkCRL = defaultCheckCRL;
        this.certificateCRL = defaultCertificateCRL;
    }

    /**
     * c: SSL_CTX_load_verify_locations
     */
    public int loadVerifyLocations(String CAfile, String CApath) {
        boolean reset = false;
        try {
            if(ctx == null) {
                reset = true;
                ctx = new Store();
                this.param.inherit(ctx.param);
                param.inherit(VerifyParameter.lookup("default"));
                this.cleanup = ctx.cleanup;
                if(ctx.checkIssued != null && ctx.checkIssued != Store.CheckIssuedFunction.EMPTY) {
                    this.checkIssued = ctx.checkIssued;
                }
                if(ctx.getIssuer != null && ctx.getIssuer != Store.GetIssuerFunction.EMPTY) {
                    this.getIssuer = ctx.getIssuer;
                }

                if(ctx.verifyCallback != null && ctx.verifyCallback != Store.VerifyCallbackFunction.EMPTY) {
                    this.verifyCallback = ctx.verifyCallback;
                }

                if(ctx.verify != null && ctx.verify != Store.VerifyFunction.EMPTY) {
                    this.verify = ctx.verify;
                }

                if(ctx.checkRevocation != null && ctx.checkRevocation != Store.CheckRevocationFunction.EMPTY) {
                    this.checkRevocation = ctx.checkRevocation;
                }

                if(ctx.getCRL != null && ctx.getCRL != Store.GetCRLFunction.EMPTY) {
                    this.getCRL = ctx.getCRL;
                }

                if(ctx.checkCRL != null && ctx.checkCRL != Store.CheckCRLFunction.EMPTY) {
                    this.checkCRL = ctx.checkCRL;
                }

                if(ctx.certificateCRL != null && ctx.certificateCRL != Store.CertificateCRLFunction.EMPTY) {
                    this.certificateCRL = ctx.certificateCRL;
                }
            }

            int ret = ctx.loadLocations(CAfile, CApath);
            if(ret == 0 && reset) resetSettingsToWithoutStore();

            return ret;
        } catch(Exception e) {
            if(reset) {
                resetSettingsToWithoutStore();
            }
            return 0;
        }
    }

    /**
     * c: X509_STORE_CTX_purpose_inherit
     */
    public int purposeInherit(int defaultPurpose,int purpose, int trust) { 
        int idx;
        if(purpose == 0) {
            purpose = defaultPurpose;
        }
        if(purpose != 0) {
            idx = Purpose.getByID(purpose);
            if(idx == -1) {
                X509Error.addError(X509Utils.X509_R_UNKNOWN_PURPOSE_ID);
                return 0;
            }
            Purpose ptmp = Purpose.getFirst(idx);
            if(ptmp.trust == X509Utils.X509_TRUST_DEFAULT) {
                idx = Purpose.getByID(defaultPurpose);
                if(idx == -1) {
                    X509Error.addError(X509Utils.X509_R_UNKNOWN_PURPOSE_ID);
                    return 0;
                }
                ptmp = Purpose.getFirst(idx);
            }
            if(trust == 0) {
                trust = ptmp.trust;
            }
        }
        if(trust != 0) {
            idx = Trust.getByID(trust);
            if(idx == -1) {
                X509Error.addError(X509Utils.X509_R_UNKNOWN_TRUST_ID);
                return 0;
            }
        }

        if(purpose != 0 && param.purpose == 0) {
            param.purpose = purpose;
        }
        if(trust != 0 && param.trust == 0) {
            param.trust = trust;
        }
        return 1;
    } 

    /**
     * c: X509_STORE_CTX_set_flags
     */
    public void setFlags(long flags) {
        param.setFlags(flags);
    } 

    /**
     * c: X509_STORE_CTX_set_time
     */
    public void setTime(long flags,Date t) {
        param.setTime(t);
    } 

    /**
     * c: X509_STORE_CTX_set_verify_cb
     */
    public void setVerifyCallback(Store.VerifyCallbackFunction verifyCallback) {
        this.verifyCallback = verifyCallback;
    } 

    /**
     * c: X509_STORE_CTX_get0_policy_tree
     */
    PolicyTree getPolicyTree() {
        return tree;
    }

    /**
     * c: X509_STORE_CTX_get_explicit_policy
     */
    public int getExplicitPolicy() { 
        return explicitPolicy;
    } 

    /**
     * c: X509_STORE_CTX_get0_param
     */
    public VerifyParameter getParam() { 
        return param; 
    } 

    /**
     * c: X509_STORE_CTX_set0_param
     */
    public void setParam(VerifyParameter param) {
        this.param = param;
    } 

    /**
     * c: X509_STORE_CTX_set_default
     */
    public int setDefault(String name) { 
        VerifyParameter p = VerifyParameter.lookup(name);
        if(p == null) {
            return 0;
        }
        return param.inherit(p);
    }

    /**
     * c: X509_STORE_get_by_subject (it gets X509_STORE_CTX as the first parameter)
     */
    public int getBySubject(int type,Name name,X509Object[] ret) throws Exception {
        Store c = ctx;

        X509Object tmp = X509Object.retrieveBySubject(c.objs,type,name);
        if(tmp == null) {
            for(int i=currentMethod; i<c.certificateMethods.size(); i++) {
                Lookup lu = c.certificateMethods.get(i);
                X509Object[] stmp = new X509Object[1];
                int j = lu.bySubject(type,name,stmp);
                if(j<0) {
                    currentMethod = i;
                    return j;
                } else if(j>0) {
                    tmp = stmp[0];
                    break;
                }
            }
            currentMethod = 0;
            if(tmp == null) {
                return 0;
            }
        }
        ret[0] = tmp;
        return 1;
    }

    /**
     * c: X509_verify_cert
     */
    public int verifyCertificate() throws Exception {
        X509AuxCertificate x,xtmp=null,chain_ss = null;
        //X509_NAME xn;
        int bad_chain = 0;
        int depth,i,ok=0;
        int num;
        Store.VerifyCallbackFunction cb;
        List<X509AuxCertificate> sktmp = null;
        if(certificate == null) {
            X509Error.addError(X509Utils.X509_R_NO_CERT_SET_FOR_US_TO_VERIFY);
            return -1;
        }
        cb=verifyCallback;

        /* first we make sure the chain we are going to build is
         * present and that the first entry is in place */

        if(null == chain) {
            chain = new ArrayList<X509AuxCertificate>();
            chain.add(certificate);
            lastUntrusted = 1;
        }

        /* We use a temporary STACK so we can chop and hack at it */

        if(untrusted != null) {
            sktmp = new ArrayList<X509AuxCertificate>(untrusted);
        }
        num = chain.size();
        x = chain.get(num-1);
        depth = param.depth;
        for(;;) {
            if(depth < num) {
                break;
            }

            if(checkIssued.call(this,x,x) != 0) {
                break;
            }

            if(untrusted != null) {
                xtmp = findIssuer(sktmp,x);
                if(xtmp != null) {
                    chain.add(xtmp);
                    sktmp.remove(xtmp);
                    lastUntrusted++;
                    x = xtmp;
                    num++;
                    continue;
                }
            }
            break;
        }

        /* at this point, chain should contain a list of untrusted
         * certificates.  We now need to add at least one trusted one,
         * if possible, otherwise we complain. */

        /* Examine last certificate in chain and see if it
         * is self signed.
         */

        i = chain.size();
        x = chain.get(i-1);
        
        if(checkIssued.call(this,x,x) != 0) {
            /* we have a self signed certificate */
            if(chain.size() == 1) {
                /* We have a single self signed certificate: see if
                 * we can find it in the store. We must have an exact
                 * match to avoid possible impersonation.
                 */
                X509AuxCertificate[] p_xtmp = new X509AuxCertificate[]{xtmp};
                ok = getIssuer.call(p_xtmp,this,x);
                xtmp = p_xtmp[0];
                if(ok <= 0 || !x.equals(xtmp)) {
                    error = X509Utils.V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT;
                    currentCertificate = x;
                    errorDepth = i-1;
                    bad_chain = 1;
                    ok = cb.call(new Integer(0),this);
                    if(ok == 0) {
                        return ok;
                    }
                } else {
                    /* We have a match: replace certificate with store version
                     * so we get any trust settings.
                     */
                    x = xtmp;
                    chain.set(i-1,x);
                    lastUntrusted = 0;
                }
            } else {
                /* extract and save self signed certificate for later use */
                chain_ss = chain.remove(chain.size()-1);
                lastUntrusted--;
                num--;
                x = chain.get(num-1);
            }
        }
        /* We now lookup certs from the certificate store */
        for(;;) {
            /* If we have enough, we break */
            if(depth<num) {
                break;
            }
            //xn = new X509_NAME(x.getIssuerX500Principal());
            /* If we are self signed, we break */
            if(checkIssued.call(this,x,x) != 0) {
                break;
            }
            X509AuxCertificate[] p_xtmp = new X509AuxCertificate[]{xtmp};
            ok = getIssuer.call(p_xtmp,this,x);
            xtmp = p_xtmp[0];
            if(ok < 0) {
                return ok;
            }
            if(ok == 0) {
                break;
            }
            x = xtmp;
            chain.add(x);
            num++;
        }
        
        /* we now have our chain, lets check it... */

        //xn = new X509_NAME(x.getIssuerX500Principal());
        /* Is last certificate looked up self signed? */
        if(checkIssued.call(this,x,x) == 0) {
            if(chain_ss == null || checkIssued.call(this,x,chain_ss) == 0) {
                if(lastUntrusted >= num) {
                    error = X509Utils.V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY;
                } else {
                    error = X509Utils.V_ERR_UNABLE_TO_GET_ISSUER_CERT;
                }
                currentCertificate = x;
            } else {
                chain.add(chain_ss);
                num++;
                lastUntrusted = num;
                currentCertificate = chain_ss;
                error = X509Utils.V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
                chain_ss = null;
            }
            errorDepth = num-1;
            bad_chain = 1;
            ok = cb.call(new Integer(0),this);
            if(ok == 0) {
                return ok;
            }
        }

        /* We have the chain complete: now we need to check its purpose */
        ok = checkChainExtensions();
        if(ok == 0) {
            return ok;
        }

        /* TODO: Check name constraints (from 1.0.0) */

        /* The chain extensions are OK: check trust */
        if(param.trust > 0) {
            ok = checkTrust();
        }
        if(ok == 0) {
            return ok;
        }

        /* Check revocation status: we do this after copying parameters
         * because they may be needed for CRL signature verification.
         */
        ok = checkRevocation.call(this);
        if(ok == 0) {
            return ok;
        }

        /* At this point, we have a chain and need to verify it */
        if(verify != null && verify != Store.VerifyFunction.EMPTY) {
            ok = verify.call(this);
        } else {
            ok = internalVerify.call(this);
        }
        if(ok == 0) {
            return ok;
        }
        
        /* TODO: RFC 3779 path validation, now that CRL check has been done (from 1.0.0) */

        /* If we get this far evaluate policies */
        if(bad_chain == 0 && (param.flags & X509Utils.V_FLAG_POLICY_CHECK) != 0) {
            ok = checkPolicy.call(this);
        }
        return ok;
    }


    private final static Set<String> CRITICAL_EXTENSIONS = new HashSet<String>();
    static {
        CRITICAL_EXTENSIONS.add("2.16.840.1.113730.1.1"); // netscape cert type, NID 71
        CRITICAL_EXTENSIONS.add("2.5.29.15"); // key usage, NID 83
        CRITICAL_EXTENSIONS.add("2.5.29.17"); // subject alt name, NID 85
        CRITICAL_EXTENSIONS.add("2.5.29.19"); // basic constraints, NID 87
        CRITICAL_EXTENSIONS.add("2.5.29.37"); // ext key usage, NID 126
        CRITICAL_EXTENSIONS.add("1.3.6.1.5.5.7.1.14"); // proxy cert info, NID 661
    }

    private static boolean supportsCriticalExtension(String oid) {
        return CRITICAL_EXTENSIONS.contains(oid);
    }

    private static boolean unhandledCritical(X509Extension xx) {
        if(xx.getCriticalExtensionOIDs() == null || xx.getCriticalExtensionOIDs().size() == 0) {
            return false;
        }
        for(String ss : xx.getCriticalExtensionOIDs()) {
            if(!supportsCriticalExtension(ss)) {
                return true;
            }
        }
        return false;
    }

    /**
     * c: check_chain_extensions
     */
    public int checkChainExtensions() throws Exception {
        int ok=0, must_be_ca;
        X509AuxCertificate x;
        Store.VerifyCallbackFunction cb;
        int proxy_path_length = 0;
        int allow_proxy_certs = (param.flags & X509Utils.V_FLAG_ALLOW_PROXY_CERTS) != 0 ? 1 : 0;
        cb = verifyCallback;
        must_be_ca = -1;

        try {
            if (System.getenv("OPENSSL_ALLOW_PROXY_CERTS") != null && !"false".equalsIgnoreCase(System.getenv("OPENSSL_ALLOW_PROXY_CERTS"))) {
                allow_proxy_certs = 1;
            }
        } catch (Error e) {
            // just ignore if we can't use System.getenv
        }

        for(int i = 0; i<lastUntrusted;i++) {
            int ret;
            x = chain.get(i);
            if((param.flags & X509Utils.V_FLAG_IGNORE_CRITICAL) == 0 && unhandledCritical(x)) {
                error = X509Utils.V_ERR_UNHANDLED_CRITICAL_EXTENSION;
                errorDepth = i;
                currentCertificate = x;
                ok = cb.call(new Integer(0),this);
                if(ok == 0) {
                    return ok;
                }
            }
            if(allow_proxy_certs == 0 && x.getExtensionValue("1.3.6.1.5.5.7.1.14") != null) {
                error = X509Utils.V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED;
                errorDepth = i;
                currentCertificate = x;
                ok = cb.call(new Integer(0),this);
                if(ok == 0) {
                    return ok;
                }
            }

            ret = Purpose.checkCA(x);
            switch(must_be_ca) {
            case -1:
                if((param.flags & X509Utils.V_FLAG_X509_STRICT) != 0 && ret != 1 && ret != 0) {
                    ret = 0;
                    error = X509Utils.V_ERR_INVALID_CA;
                } else {
                    ret = 1;
                }
                break;
            case 0:
                if(ret != 0) {
                    ret = 0;
                    error = X509Utils.V_ERR_INVALID_NON_CA;
                } else {
                    ret = 1;
                }
                break;
            default:
                if(ret == 0 || ((param.flags & X509Utils.V_FLAG_X509_STRICT) != 0 && ret != 1)) {
                    ret = 0;
                    error = X509Utils.V_ERR_INVALID_CA;
                } else {
                    ret = 1;
                }
                break;
            }
            if(ret == 0) {
                errorDepth = i;
                currentCertificate = x;
                ok = cb.call(new Integer(0),this);
                if(ok == 0) {
                    return ok;
                }
            }
            if(param.purpose > 0) {
                ret = Purpose.checkPurpose(x,param.purpose, must_be_ca > 0 ? 1 : 0);
                if(ret == 0 || ((param.flags & X509Utils.V_FLAG_X509_STRICT) != 0 && ret != 1)) {
                    error = X509Utils.V_ERR_INVALID_PURPOSE;
                    errorDepth = i;
                    currentCertificate = x;
                    ok = cb.call(new Integer(0),this);
                    if(ok == 0) {
                        return ok;
                    }
                }
            }

            if(i > 1 && x.getBasicConstraints() != -1 && x.getBasicConstraints() != Integer.MAX_VALUE && (i > (x.getBasicConstraints() + proxy_path_length + 1))) {
                error = X509Utils.V_ERR_PATH_LENGTH_EXCEEDED;
                errorDepth = i;
                currentCertificate = x;
                ok = cb.call(new Integer(0),this);
                if(ok == 0) {
                    return ok;
                }
            }

            if(x.getExtensionValue("1.3.6.1.5.5.7.1.14") != null) {
                DERSequence pci = (DERSequence)new ASN1InputStream(x.getExtensionValue("1.3.6.1.5.5.7.1.14")).readObject();
                if(pci.size() > 0 && pci.getObjectAt(0) instanceof DERInteger) {
                    int pcpathlen = ((DERInteger)pci.getObjectAt(0)).getValue().intValue();
                    if(i > pcpathlen) {
                        error = X509Utils.V_ERR_PROXY_PATH_LENGTH_EXCEEDED;
                        errorDepth = i;
                        currentCertificate = x;
                        ok = cb.call(new Integer(0),this);
                        if(ok == 0) {
                            return ok;
                        }
                    }
                }
                proxy_path_length++;
                must_be_ca = 0;
            } else {
                must_be_ca = 1;
            }
        }
        return 1;
    }

    /**
     * c: X509_check_trust
     */
    public int checkTrust() throws Exception {
        int i,ok;
        X509AuxCertificate x;
        Store.VerifyCallbackFunction cb;
        cb = verifyCallback;
        i = chain.size()-1;
        x = chain.get(i);
        ok = Trust.checkTrust(x,param.trust,0);
        if(ok == X509Utils.X509_TRUST_TRUSTED) {
            return 1;
        }
        errorDepth = 1;
        currentCertificate = x;
        if(ok == X509Utils.X509_TRUST_REJECTED) {
            error = X509Utils.V_ERR_CERT_REJECTED;
        } else {
            error = X509Utils.V_ERR_CERT_UNTRUSTED;
        }
        return cb.call(new Integer(0),this);
    }

    /**
     * c: check_cert_time
     */
    public int checkCertificateTime(X509AuxCertificate x) throws Exception {
        Date ptime = null;

        if((param.flags & X509Utils.V_FLAG_USE_CHECK_TIME) != 0) {
            ptime = this.param.checkTime;
        } else {
            ptime = Calendar.getInstance().getTime();
        }
        if(!x.getNotBefore().before(ptime)) {
            error = X509Utils.V_ERR_CERT_NOT_YET_VALID;
            currentCertificate = x;
            if(verifyCallback.call(new Integer(0),this) == 0) {
                return 0;
            }
        }
        if(!x.getNotAfter().after(ptime)) {
            error = X509Utils.V_ERR_CERT_HAS_EXPIRED;
            currentCertificate = x;
            if(verifyCallback.call(new Integer(0),this) == 0) {
                return 0;
            }
        }
        return 1;
    }

    /**
     * c: check_cert
     */
    public int checkCertificate() throws Exception {
        X509CRL[] crl = new X509CRL[1];
        X509AuxCertificate x;
        int ok,cnum;
        cnum = errorDepth;
        x = chain.get(cnum);
        currentCertificate = x;
        ok = getCRL.call(this,crl,x);
        if(ok == 0) {
            error = X509Utils.V_ERR_UNABLE_TO_GET_CRL;
            ok = verifyCallback.call(new Integer(0), this);
            currentCRL = null;
            return ok;
        }
        currentCRL = crl[0];
        ok = checkCRL.call(this, crl[0]);
        if(ok == 0) {
            currentCRL = null;
            return ok;
        }
        ok = certificateCRL.call(this,crl[0],x);
        currentCRL = null;
        return ok;
    }

    /**
     * c: check_crl_time
     */
    public int checkCRLTime(X509CRL crl, int notify) throws Exception {
        currentCRL = crl;
        Date ptime = null;

        if((param.flags & X509Utils.V_FLAG_USE_CHECK_TIME) != 0) {
            ptime = this.param.checkTime;
        } else {
            ptime = Calendar.getInstance().getTime();
        }
        
        if(!crl.getThisUpdate().before(ptime)) {
            error=X509Utils.V_ERR_CRL_NOT_YET_VALID;
            if(notify == 0 || verifyCallback.call(new Integer(0),this) == 0) {
                return 0;
            }
        }
        if(crl.getNextUpdate() != null && !crl.getNextUpdate().after(ptime)) {
            error=X509Utils.V_ERR_CRL_HAS_EXPIRED;
            if(notify == 0 || verifyCallback.call(new Integer(0),this) == 0) {
                return 0;
            }
        }

        currentCRL = null;
        return 1;
    }

    /**
     * c: get_crl_sk
     */
    public int getCRLStack(X509CRL[] pcrl, Name nm, List<X509CRL> crls) throws Exception { 
        X509CRL best_crl = null;
        if(null != crls) {
            for(X509CRL crl : crls) {
                if(!nm.isEqual(crl.getIssuerX500Principal())) {
                    continue;
                }
                if(checkCRLTime(crl,0) != 0) {
                    pcrl[0] = crl;
                    return 1;
                }
                best_crl = crl;
            }
        }
        if(best_crl != null) {
            pcrl[0] = best_crl;
        }
        return 0;
    }

    /**
     * c: get_issuer_sk
     */
    public final static Store.GetIssuerFunction getIssuerStack = new Store.GetIssuerFunction() { 
            public int call(Object a1, Object a2, Object a3) throws Exception {
                X509AuxCertificate[] issuer = (X509AuxCertificate[])a1;
                StoreContext ctx = (StoreContext)a2;
                X509AuxCertificate x = (X509AuxCertificate)a3;
                issuer[0] = ctx.findIssuer(ctx.otherContext,x);
                if(issuer[0] != null) {
                    return 1;
                } else {
                    return 0;
                }
            }
        };

    /**
     * c: check_issued
     */
    public final static Store.CheckIssuedFunction defaultCheckIssued = new Store.CheckIssuedFunction() { 
            public int call(Object a1, Object a2, Object a3) throws Exception {
                StoreContext ctx = (StoreContext)a1;
                X509AuxCertificate x = (X509AuxCertificate)a2;
                X509AuxCertificate issuer = (X509AuxCertificate)a3;
                int ret = X509Utils.checkIfIssuedBy(issuer,x);
                if(ret == X509Utils.V_OK) {
                    return 1;
                }
                if((ctx.param.flags & X509Utils.V_FLAG_CB_ISSUER_CHECK) == 0) {
                    return 0;
                }
                ctx.error = ret;
                ctx.currentCertificate = x;
                ctx.currentIssuer = issuer;
                return ctx.verifyCallback.call(new Integer(0),ctx);
            }
        };

    /**
     * c: null_callback
     */
    public final static Store.VerifyCallbackFunction NullCallback = new Store.VerifyCallbackFunction() { 
            public int call(Object a1, Object a2) {
                return ((Integer)a1).intValue();
            }
        };

    /**
     * c: internal_verify
     */
    public final static Store.VerifyFunction internalVerify = new Store.VerifyFunction() { 
            public int call(Object a1) throws Exception {
                StoreContext ctx = (StoreContext)a1;
                Store.VerifyCallbackFunction cb = ctx.verifyCallback;
                int n = ctx.chain.size();
                ctx.errorDepth = n-1;
                n--;
                X509AuxCertificate xi = ctx.chain.get(n);
                X509AuxCertificate xs = null;
                int ok = 0;
                if(ctx.checkIssued.call(ctx,xi,xi) != 0) {
                    xs = xi;
                } else {
                    if(n<=0) {
                        ctx.error = X509Utils.V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
                        ctx.currentCertificate = xi;
                        ok = cb.call(new Integer(0),ctx);
                        return ok;
                    } else {
                        n--;
                        ctx.errorDepth = n;
                        xs = ctx.chain.get(n);
                    }
                }
                while(n>=0) {
                    ctx.errorDepth = n;
                    if(!xs.isValid()) {
                        try {
                            xs.verify(xi.getPublicKey());
                        } catch(Exception e) {
                            /*
                            System.err.println("n: " + n);
                            System.err.println("verifying: " + xs);
                            System.err.println("verifying with issuer?: " + xi);
                            System.err.println("verifying with issuer.key?: " + xi.getPublicKey());
                            System.err.println("exception: " + e);
                            */
                            ctx.error = X509Utils.V_ERR_CERT_SIGNATURE_FAILURE;
                            ctx.currentCertificate = xs;
                            ok = cb.call(new Integer(0),ctx);
                            if(ok == 0) {
                                return ok;
                            }
                        }
                    }
                    xs.setValid(true);
                    ok = ctx.checkCertificateTime(xs);
                    if(ok == 0) {
                        return ok;
                    }
                    ctx.currentIssuer = xi;
                    ctx.currentCertificate = xs;
                    ok = cb.call(new Integer(1),ctx);
                    if(ok == 0) {
                        return ok;
                    }
                    n--;
                    if(n>=0) {
                        xi = xs;
                        xs = ctx.chain.get(n);
                    }
                }
                ok = 1;
                return ok;
            }
        };

    /**
     * c: check_revocation
     */
    public final static Store.CheckRevocationFunction defaultCheckRevocation = new Store.CheckRevocationFunction() { 
            public int call(Object a1) throws Exception {
                StoreContext ctx = (StoreContext)a1;
                int last,ok=0;
                if((ctx.param.flags & X509Utils.V_FLAG_CRL_CHECK) == 0) {
                    return 1;
                }
                if((ctx.param.flags & X509Utils.V_FLAG_CRL_CHECK_ALL) != 0) {
                    last = ctx.chain.size() -1;
                } else {
                    last = 0;
                }
                for(int i=0;i<=last;i++) {
                    ctx.errorDepth = i;
                    ok = ctx.checkCertificate();
                    if(ok == 0) {
                        return 0;
                    }
                }
                return 1;
            }
        };

    /**
     * c: get_crl
     */
    public final static Store.GetCRLFunction defaultGetCRL = new Store.GetCRLFunction() { 
            public int call(Object a1, Object a2, Object a3) throws Exception {
                StoreContext ctx = (StoreContext)a1;
                X509CRL[] pcrl = (X509CRL[])a2;
                X509AuxCertificate x = (X509AuxCertificate)a3;
                Name nm = new Name(x.getIssuerX500Principal());
                X509CRL[] crl = new X509CRL[1];
                int ok = ctx.getCRLStack(crl,nm,ctx.crls);
                if(ok != 0) {
                    pcrl[0] = crl[0];
                    return 1;
                }
                X509Object[] xobj = new X509Object[1];
                ok = ctx.getBySubject(X509Utils.X509_LU_CRL,nm,xobj);
                if(ok == 0) {
                    if(crl[0] != null) {
                        pcrl[0] = crl[0];
                        return 1;
                    }
                    return 0;
                }
                pcrl[0] = (X509CRL)(((CRL)xobj[0]).crl);
                return 1;
            }
        };

    /**
     * c: check_crl
     */
    public final static Store.CheckCRLFunction defaultCheckCRL = new Store.CheckCRLFunction() { 
            public int call(Object a1, Object a2) throws Exception {
                StoreContext ctx = (StoreContext)a1;
                final X509CRL crl = (X509CRL)a2;
                X509AuxCertificate issuer = null;
                int ok = 0,chnum,cnum;
                cnum = ctx.errorDepth;
                chnum = ctx.chain.size()-1;
                if(cnum < chnum) {
                    issuer = ctx.chain.get(cnum+1);
                } else {
                    issuer = ctx.chain.get(chnum);
                    if(ctx.checkIssued.call(ctx,issuer,issuer) == 0) {
                        ctx.error = X509Utils.V_ERR_UNABLE_TO_GET_CRL_ISSUER;
                        ok = ctx.verifyCallback.call(new Integer(0),ctx);
                        if(ok == 0) {
                            return ok;
                        }
                    }
                }

                if (issuer != null) {
                    if (issuer.getKeyUsage() != null && !issuer.getKeyUsage()[6]) {
                        ctx.error = X509Utils.V_ERR_KEYUSAGE_NO_CRL_SIGN;
                        ok = ctx.verifyCallback.call(new Integer(0), ctx);
                        if (ok == 0) {
                            return ok;
                        }
                    }
                    final PublicKey ikey = issuer.getPublicKey();
                    if (ikey == null) {
                        ctx.error = X509Utils.V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
                        ok = ctx.verifyCallback.call(new Integer(0), ctx);
                        if (ok == 0) {
                            return ok;
                        }
                    } else {
                        try {
                            crl.verify(ikey);
                        } catch (Exception ignored) {
                            ctx.error = X509Utils.V_ERR_CRL_SIGNATURE_FAILURE;
                            ok = ctx.verifyCallback.call(new Integer(0), ctx);
                            if (ok == 0) {
                                return ok;
                            }
                        }
                    }
                }

                ok = ctx.checkCRLTime(crl,1);
                if(ok == 0) {
                    return ok;
                }
                return 1;
            }
        };

    /**
     * c: cert_crl
     */
    public final static Store.CertificateCRLFunction defaultCertificateCRL = new Store.CertificateCRLFunction() { 
            public int call(Object a1, Object a2, Object a3) throws Exception {
                StoreContext ctx = (StoreContext)a1;
                X509CRL crl = (X509CRL)a2;
                X509AuxCertificate x = (X509AuxCertificate)a3;
                int ok;
                if(crl.getRevokedCertificate(x.getSerialNumber()) != null) {
                    ctx.error = X509Utils.V_ERR_CERT_REVOKED;
                    ok = ctx.verifyCallback.call(new Integer(0), ctx);
                    if(ok == 0) {
                        return 0;
                    }
                }
                if((ctx.param.flags & X509Utils.V_FLAG_IGNORE_CRITICAL) != 0) {
                    return 1;
                }

                if(crl.getCriticalExtensionOIDs() != null && crl.getCriticalExtensionOIDs().size()>0) {
                    ctx.error = X509Utils.V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION;
                    ok = ctx.verifyCallback.call(new Integer(0), ctx);
                    if(ok == 0) {
                        return 0;
                    }
                }
                return 1;
            }
        };

    /**
     * c: check_policy
     */
    public final static CheckPolicyFunction defaultCheckPolicy = new CheckPolicyFunction() { 
            public int call(Object a1) throws Exception {
                return 1;
            }
        };
}// X509_STORE_CTX
